package jp.sourceforge.qrcode;

import java.util.Vector;

import jp.sourceforge.qrcode.data.QRCodeSymbol;
import jp.sourceforge.qrcode.ecc.BCH15_5;
import jp.sourceforge.qrcode.ecc.ReedSolomon;
import jp.sourceforge.qrcode.exception.*;
import jp.sourceforge.qrcode.reader.QRCodeDataBlockReader;
import jp.sourceforge.qrcode.reader.QRCodeImageReader;

public class QRCodeDecoder {
 int internalScale = 1; //default
 QRCodeSymbol symbol;
 final boolean debug = true;
 
 public QRCodeDecoder() {}
 
 public QRCodeDecoder(int imageScale) {
  internalScale = imageScale;
 }
 
 public String decode(int[][] image) throws DecodingFailedException, UnsupportedVersionException {  
  if (debug) System.out.println("Decoding started.");
  
  try {
   symbol = getQRCodeSymbol(image);
   if (debug) System.out.println("QRCode symbol be created.");
  } catch (SymbolNotFoundException e) {
   if (debug) e.printStackTrace();
   throw new DecodingFailedException();
  }
  
  if (debug) System.out.println("Reading symbol.");  
  boolean[] formatInformation = getFormatInformation(symbol);
  symbol.setFormatInformation(formatInformation);
  if (debug) System.out.println("Version: " + symbol.getVersionReference());
  
  String maskPattern = Integer.toString(symbol.getMaskPatternReferer(), 2);
  int length = maskPattern.length();
  for (int i = 0; i < 3 - length; i++)
   maskPattern = "0" + maskPattern;
  
  if (debug) System.out.println("Mask pattern: " + maskPattern);
  if (debug) System.out.println("Unmasking.");
  unmask(symbol);
  
  int[] blocks = getBlocks(symbol);
  if (debug) System.out.println("blocks.length: " + blocks.length);
  for (int i = 0; i < blocks.length; i++) {
   if (debug) System.out.print(blocks[i] + " ");
  }
  if (debug) System.out.println("");
  
  if (debug) System.out.println("Correcting data errors.");
  int[] dataBlocks = getCorrectedDataBlocks(blocks);
  if (debug) System.out.println("CorrectedDataBlocks.length: " + dataBlocks.length);
  for (int i = 0; i < dataBlocks.length; i++) {
   if (debug) System.out.print(dataBlocks[i] + " ");
  }
  if (debug) System.out.println("");
    
  String decodedString = "";
  try {
   decodedString = getDecodedString(dataBlocks, symbol.getVersion());
  } catch (IllegalDataBlockException e) {
   if (debug) e.printStackTrace();
   throw new DecodingFailedException();
  }
  
  if (debug) System.out.println("Decoding finished.");
  return decodedString;
 }
 
 boolean[][] processImage(int[][] image) {
  imageToGrayScale(image);
  boolean[][] bitmap = grayScaleToBitmap(image);
  if (internalScale > 1) {
   boolean[][] bitmapEx = extendBitmap(bitmap, internalScale);
   return bitmapEx;
  } else {
   return bitmap;
  }
 }
 
 void imageToGrayScale(int[][] image) {
  for (int y = 0; y < image[0].length; y++) {
   for (int x = 0; x < image.length; x++) {
    int r = image[x][y] >> 16 & 0xFF;
    int g = image[x][y] >> 8 & 0xFF;
    int b = image[x][y] & 0xFF;
    int m = (r * 30 + g * 59 + b * 11) / 100;
    image[x][y] = m;
   }
  }
 }
 
 boolean[][] grayScaleToBitmap(int[][] grayScale) {
  int[][] middle = findAreaMiddle(grayScale);
  int sqrtNumArea = middle.length;
  int areaWidth   = grayScale.length / sqrtNumArea;
  int areaHeight  = grayScale[0].length / sqrtNumArea;
  
  boolean[][] bitmap = new boolean[grayScale.length][grayScale[0].length];
  for (int ay = 0; ay < sqrtNumArea; ay++) {
   for (int ax = 0; ax < sqrtNumArea; ax++) {
    for (int dy = 0; dy < areaHeight; dy++) {
     for (int dx = 0; dx < areaWidth; dx++) {
      bitmap[areaWidth * ax + dx][areaHeight * ay + dy] = (grayScale[areaWidth * ax + dx][areaHeight * ay + dy] < middle[ax][ay]) ? true : false;
     }
    }
   }
  }
  return bitmap;
 }
 
 int[][] findAreaMiddle(int[][] image) {
  final int numSqrtArea = 4; //4x4 ((min + max) / 2)
  int areaWidth = image.length / numSqrtArea;
  int areaHeight = image[0].length / numSqrtArea;
  int[][][] minmax = new int[numSqrtArea][numSqrtArea][2];
  for (int ay = 0; ay < numSqrtArea; ay++) {
   for (int ax = 0; ax < numSqrtArea; ax++) {
    minmax[ax][ay][0] = 0xFF;
    for (int dy = 0; dy < areaHeight; dy++) {
     for (int dx = 0; dx < areaWidth; dx++) {
      int target = image[areaWidth * ax + dx][areaHeight * ay + dy];
      if (target < minmax[ax][ay][0]) minmax[ax][ay][0] = target;
      if (target > minmax[ax][ay][1]) minmax[ax][ay][1] = target;
     }
    }
   }
  }
  int[][] middle =  new int[numSqrtArea][numSqrtArea];
  for (int ay = 0; ay < numSqrtArea; ay++) {
   for (int ax = 0; ax < numSqrtArea; ax++) {
    middle[ax][ay] = (minmax[ax][ay][0] + minmax[ax][ay][1]) / 2;
   }
  }

  return middle;
 }
 
 boolean[][] extendBitmap(boolean[][] bitmap, int scale) {
  boolean[][] bitmap2x = new boolean[bitmap.length * scale][bitmap[0].length * scale];
  for (int y = 0; y < bitmap[0].length; y++) {
   for (int x = 0; x < bitmap.length; x++) {
    if (bitmap[x][y] == true) {
     for (int sx = 0; sx < scale; sx++)
      for (int sy = 0; sy < scale; sy++)
       bitmap2x[x * scale + sx][y * scale + sy] = true;
    }
     
   }
  }
  return bitmap2x;
 }

 QRCodeSymbol getQRCodeSymbol(int[][] image) throws SymbolNotFoundException, UnsupportedVersionException {
   if (debug) System.out.println("Creating bitmap.");
  boolean[][] bitmap = processImage(image);
  QRCodeImageReader reader = new QRCodeImageReader();

  QRCodeSymbol symbol = null;
  try {
   symbol = reader.getQRCodeSymbol(bitmap);
  } catch (FinderPatternNotFoundException e1) {
   throw new SymbolNotFoundException();
  } catch (VersionInformationException e2) {
   throw new SymbolNotFoundException();
  } catch (AlignmentPatternEdgeNotFoundException e3) {
   throw new SymbolNotFoundException();
  }
  return symbol;
 }
 
 boolean[] getFormatInformation(QRCodeSymbol qRCodeMatrix) {
  boolean[] modules = new boolean[15];
  
  for (int i = 0; i <= 5; i++)
   modules[i] = qRCodeMatrix.getElement(8, i);
  
  modules[6] = qRCodeMatrix.getElement(8, 7);
  modules[7] = qRCodeMatrix.getElement(8, 8);
  modules[8] = qRCodeMatrix.getElement(7, 8);
  
  for (int i = 9; i <= 14; i++)
   modules[i] = qRCodeMatrix.getElement(14 - i, 8);
  
  int maskPattern = 0x5412;
  
  for (int i = 0; i <= 14; i++) {
   boolean xorBit = false;
   if (((maskPattern >>> i) & 1) == 1)
    xorBit = true;
   else
    xorBit = false;
   
   if (modules[i] == xorBit)
    modules[i] = false;
   else
    modules[i] = true;
  }
  
  BCH15_5 corrector = new BCH15_5(modules);
  boolean[] output = corrector.correct();
  int numError = corrector.getNumCorrectedError();
  if (numError > 0) {
   if (debug) System.out.println(String.valueOf(numError) + " format errors corrected.");
  }
  
  boolean[] formatInformation = new boolean[5];
  for (int i = 0; i < 5; i++)
   formatInformation[i] = output[10 + i];
  
  return formatInformation;
 }
 
 void unmask(QRCodeSymbol symbol) {
  int maskPatternReferer = symbol.getMaskPatternReferer();  
  boolean[][] maskPattern = generateMaskPattern(symbol);
  int size = symbol.getWidth();
  
  for (int y = 0; y < size; y++) {
   for (int x = 0; x < size; x++) {
    if (maskPattern[x][y] == true) {
     symbol.reverseElement(x, y);
    }
   }
  }
 }
 
 boolean[][] generateMaskPattern(QRCodeSymbol symbol) {
  int maskPatternReferer = symbol.getMaskPatternReferer();
  
  int width = symbol.getWidth();
  int height = symbol.getHeight();
  boolean[][] maskPattern = new boolean[width][height];
  
  for (int y = 0; y < height; y++) {
   for (int x = 0; x < width; x++) {
    if (symbol.isInFunctionPattern(x, y)) continue;
    
    switch (maskPatternReferer) {
    case 0: // 000
     if ((x + y) % 2 == 0)
      maskPattern[x][y] = true;
     break;
    case 1: // 001
     if (y % 2 == 0)
      maskPattern[x][y] = true;
     break;
    case 2: // 010
     if (x % 3 == 0)
      maskPattern[x][y] = true;
     break;
    case 3: // 011
     if ((x + y) % 3 == 0)
      maskPattern[x][y] = true;
     break;
    case 4: // 100
     if ((x / 3 + y / 2) % 2 == 0)
      maskPattern[x][y] = true;
     break;
    case 5: // 101
     if ((x * y) % 2 + (x * y) % 3 == 0)
      maskPattern[x][y] = true;
     break;
    case 6: // 110
     if (((x * y) % 2 + (x * y) % 3) % 2 == 0)
      maskPattern[x][y] = true;
     break;
    case 7: // 111
     if (((x * y) % 3 + (x + y) % 2) % 2 == 0)
      maskPattern[x][y] = true;
     break;
    }
   }
  }
  return maskPattern;
 }
 
 int[] getBlocks(QRCodeSymbol symbol) {
  int width  = symbol.getWidth();
  int height = symbol.getHeight();
  if (debug) System.out.println("SymbolWidth:  " + Integer.toString(symbol.getWidth()));
  if (debug) System.out.println("SymbolHeight: " + Integer.toString(symbol.getHeight()));
  
  int x = width - 1;
  int y = height - 1;
  Vector codeBits = new Vector();
  Vector codeWords = new Vector();
  int tempWord = 0;
  int figure = 7;
  int isNearFinish = 0;
  final boolean READ_UP = true;
  final boolean READ_DOWN = false;
  boolean direction = READ_UP;
  
  do {
   codeBits.addElement(new Boolean(symbol.getElement(x, y)));
   if (symbol.getElement(x, y) == true) {
    tempWord += 1 << figure;
   }
   
   figure--;
   
   if (figure == -1) {
    codeWords.addElement(new Integer(tempWord));
    figure = 7;
    tempWord = 0;
   }
   
   do {
    if (direction == READ_UP) {
     if ((x + isNearFinish) % 2 == 0) {
      x--;
     } else {
      if (y > 0) {
       x++;
       y--;
      } else {
       x--;
       if (x == 6) {
        x--;
        isNearFinish = 1;
       }
       direction = READ_DOWN;
      }
     }   
    } else {
     if ((x + isNearFinish) % 2 == 0) {
      x--;
     } else {
      if (y < height - 1) {
       x++;
       y++;
      } else {
       x--;
       if (x == 6){
        x--;
        isNearFinish = 1;
       }
       direction = READ_UP;
      }
     }    
    }
   } while (symbol.isInFunctionPattern(x, y));
  } while (x != -1);
  
  int[] gotWords = new int[codeWords.size()];
  for (int i = 0; i < codeWords.size(); i++) {
   Integer temp = (Integer)codeWords.elementAt(i);
   gotWords[i] = temp.intValue();
  }
  return gotWords;
 }

 int[] getCorrectedDataBlocks(int[] blocks) {
  int numErrors = 0;
  int version = symbol.getVersion();
  if (debug) System.out.println("Version: " + version);
  
  int errorCollectionLevel = symbol.getErrorCollectionLevel();
  if (debug) System.out.println("errorCollectionLevel: " + errorCollectionLevel);
  
  int dataCapacity = symbol.getDataCapacity();
  int[]  dataBlocks = new int[dataCapacity];
  if (debug) System.out.println("dataCapacity: " + dataCapacity);
  
  int numErrorCollectionCode = symbol.getNumErrorCollectionCode();
  if (debug) System.out.println("numErrorCollectionCode: " + numErrorCollectionCode);
  
  int numRSBlocks = symbol.getNumRSBlocks();
  int eccPerRSBlock = numErrorCollectionCode / numRSBlocks;
  if (debug) System.out.println("numRSBlocks: " + numRSBlocks);
  
  if (numRSBlocks == 1) {
   ReedSolomon corrector = new ReedSolomon(blocks);
   corrector.correct();
   numErrors += corrector.getNumCorrectedErrors();

   if (numErrors > 0) {
    if (debug) System.out.println(String.valueOf(numErrors) + " data errors corrected.");
   } else {
    if (debug) System.out.println("No errors found.");
   }
   
   return blocks;
  } else  {
   int numLongerRSBlocks = dataCapacity % numRSBlocks; 
   if (debug) System.out.println("numLongerRSBlocks: " + numLongerRSBlocks);
   
   if (numLongerRSBlocks == 0) {
    int lengthRSBlock = dataCapacity / numRSBlocks;
    int[][] RSBlocks = new int[numRSBlocks][lengthRSBlock];
    
    for (int i = 0; i < numRSBlocks; i++) {
     for (int j = 0; j < lengthRSBlock; j++) {
      RSBlocks[i][j] = blocks[j * numRSBlocks + i];
     }

     ReedSolomon corrector = new ReedSolomon(RSBlocks[i]);
     corrector.correct();
     numErrors += corrector.getNumCorrectedErrors();
    }
    
    int p = 0;
    for (int i = 0; i < numRSBlocks; i++) {
     for (int j = 0; j < lengthRSBlock - eccPerRSBlock; j++) {
      dataBlocks[p++] = RSBlocks[i][j];
     }
    }
   } else {
    int lengthShorterRSBlock = dataCapacity / numRSBlocks;    
    int lengthLongerRSBlock  = dataCapacity / numRSBlocks + 1;    
    int numShorterRSBlocks   = numRSBlocks - numLongerRSBlocks;
    
    int[][] shorterRSBlocks = new int[numShorterRSBlocks][lengthShorterRSBlock];
    int[][] longerRSBlocks = new int[numLongerRSBlocks][lengthLongerRSBlock];
    
    for (int i = 0; i < numRSBlocks; i++) {
     if (i < numShorterRSBlocks) {
      int mod = 0;
      for (int j = 0; j < lengthShorterRSBlock; j++) {
       if (j == lengthShorterRSBlock - eccPerRSBlock) mod = numLongerRSBlocks;
       shorterRSBlocks[i][j] = blocks[j * numRSBlocks + i + mod];
      }
      
      ReedSolomon corrector = new ReedSolomon(shorterRSBlocks[i]);
      corrector.correct();
      numErrors += corrector.getNumCorrectedErrors();

     } else {
      int mod = 0;
      for (int j = 0; j < lengthLongerRSBlock; j++) {
       if (j == lengthShorterRSBlock - eccPerRSBlock) mod = numShorterRSBlocks;
       longerRSBlocks[i - numShorterRSBlocks][j] = blocks[j * numRSBlocks + i - mod];
      }
      
      ReedSolomon corrector = new ReedSolomon(longerRSBlocks[i - numShorterRSBlocks]);
      corrector.correct();
      numErrors += corrector.getNumCorrectedErrors();
     }
    }
    
    int p = 0;
    for (int i = 0; i < numRSBlocks; i++) {
     if (i < numShorterRSBlocks) {
      for (int j = 0; j < lengthShorterRSBlock - eccPerRSBlock; j++) {
       dataBlocks[p++] = shorterRSBlocks[i][j];
      }
     } else {
      for (int j = 0; j < lengthLongerRSBlock - eccPerRSBlock; j++) {
       dataBlocks[p++] = longerRSBlocks[i - numShorterRSBlocks][j];
      }
     }
    }
   }
   
   if (numErrors > 0) {
    if (debug) System.out.println(String.valueOf(numErrors) + " data errors corrected.");
   } else {
    if (debug) System.out.println("No errors found."); 
   }
   return dataBlocks;
  }
 }
 
 String getDecodedString(int[] blocks, int version) throws IllegalDataBlockException {
  if (debug) System.out.println("Reading data blocks.");
  String dataString = null;

  QRCodeDataBlockReader reader = new QRCodeDataBlockReader(blocks, version);
  try {
   dataString = reader.getDataString();
  } catch (ArrayIndexOutOfBoundsException e) {
   if (debug) System.out.println("ERROR: Data block error. " + e);
   throw new IllegalDataBlockException();
  }
  return dataString;
 }
}

程式碼下載

創作者介紹
創作者 shadow 的頭像
shadow

資訊園

shadow 發表在 痞客邦 留言(0) 人氣()