001/*
002 * Copyright 2008 ZXing authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.itextpdf.text.pdf.qrcode;
018
019/**
020 * @author satorux@google.com (Satoru Takabayashi) - creator
021 * @author dswitkin@google.com (Daniel Switkin) - ported from C++
022 * @since 5.0.2
023 */
024public final class MaskUtil {
025
026  private MaskUtil() {
027    // do nothing
028  }
029
030  // Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
031  // give penalty to them. Example: 00000 or 11111.
032  public static int applyMaskPenaltyRule1(ByteMatrix matrix) {
033    return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false);
034  }
035
036  // Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
037  // penalty to them.
038  public static int applyMaskPenaltyRule2(ByteMatrix matrix) {
039    int penalty = 0;
040    byte[][] array = matrix.getArray();
041    int width = matrix.getWidth();
042    int height = matrix.getHeight();
043    for (int y = 0; y < height - 1; ++y) {
044      for (int x = 0; x < width - 1; ++x) {
045        int value = array[y][x];
046        if (value == array[y][x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) {
047          penalty += 3;
048        }
049      }
050    }
051    return penalty;
052  }
053
054  // Apply mask penalty rule 3 and return the penalty. Find consecutive cells of 00001011101 or
055  // 10111010000, and give penalty to them.  If we find patterns like 000010111010000, we give
056  // penalties twice (i.e. 40 * 2).
057  public static int applyMaskPenaltyRule3(ByteMatrix matrix) {
058    int penalty = 0;
059    byte[][] array = matrix.getArray();
060    int width = matrix.getWidth();
061    int height = matrix.getHeight();
062    for (int y = 0; y < height; ++y) {
063      for (int x = 0; x < width; ++x) {
064        // Tried to simplify following conditions but failed.
065        if (x + 6 < width &&
066            array[y][x] == 1 &&
067            array[y][x +  1] == 0 &&
068            array[y][x +  2] == 1 &&
069            array[y][x +  3] == 1 &&
070            array[y][x +  4] == 1 &&
071            array[y][x +  5] == 0 &&
072            array[y][x +  6] == 1 &&
073            ((x + 10 < width &&
074                array[y][x +  7] == 0 &&
075                array[y][x +  8] == 0 &&
076                array[y][x +  9] == 0 &&
077                array[y][x + 10] == 0) ||
078                (x - 4 >= 0 &&
079                    array[y][x -  1] == 0 &&
080                    array[y][x -  2] == 0 &&
081                    array[y][x -  3] == 0 &&
082                    array[y][x -  4] == 0))) {
083          penalty += 40;
084        }
085        if (y + 6 < height &&
086            array[y][x] == 1  &&
087            array[y +  1][x] == 0  &&
088            array[y +  2][x] == 1  &&
089            array[y +  3][x] == 1  &&
090            array[y +  4][x] == 1  &&
091            array[y +  5][x] == 0  &&
092            array[y +  6][x] == 1 &&
093            ((y + 10 < height &&
094                array[y +  7][x] == 0 &&
095                array[y +  8][x] == 0 &&
096                array[y +  9][x] == 0 &&
097                array[y + 10][x] == 0) ||
098                (y - 4 >= 0 &&
099                    array[y -  1][x] == 0 &&
100                    array[y -  2][x] == 0 &&
101                    array[y -  3][x] == 0 &&
102                    array[y -  4][x] == 0))) {
103          penalty += 40;
104        }
105      }
106    }
107    return penalty;
108  }
109
110  // Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
111  // penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. Examples:
112  // -   0% => 100
113  // -  40% =>  20
114  // -  45% =>  10
115  // -  50% =>   0
116  // -  55% =>  10
117  // -  55% =>  20
118  // - 100% => 100
119  public static int applyMaskPenaltyRule4(ByteMatrix matrix) {
120    int numDarkCells = 0;
121    byte[][] array = matrix.getArray();
122    int width = matrix.getWidth();
123    int height = matrix.getHeight();
124    for (int y = 0; y < height; ++y) {
125      for (int x = 0; x < width; ++x) {
126        if (array[y][x] == 1) {
127          numDarkCells += 1;
128        }
129      }
130    }
131    int numTotalCells = matrix.getHeight() * matrix.getWidth();
132    double darkRatio = (double) numDarkCells / numTotalCells;
133    return Math.abs((int) (darkRatio * 100 - 50)) / 5 * 10;
134  }
135
136  // Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask
137  // pattern conditions.
138  public static boolean getDataMaskBit(int maskPattern, int x, int y) {
139    if (!QRCode.isValidMaskPattern(maskPattern)) {
140      throw new IllegalArgumentException("Invalid mask pattern");
141    }
142    int intermediate, temp;
143    switch (maskPattern) {
144      case 0:
145        intermediate = (y + x) & 0x1;
146        break;
147      case 1:
148        intermediate = y & 0x1;
149        break;
150      case 2:
151        intermediate = x % 3;
152        break;
153      case 3:
154        intermediate = (y + x) % 3;
155        break;
156      case 4:
157        intermediate = ((y >>> 1) + (x / 3)) & 0x1;
158        break;
159      case 5:
160        temp = y * x;
161        intermediate = (temp & 0x1) + (temp % 3);
162        break;
163      case 6:
164        temp = y * x;
165        intermediate = (((temp & 0x1) + (temp % 3)) & 0x1);
166        break;
167      case 7:
168        temp = y * x;
169        intermediate = (((temp % 3) + ((y + x) & 0x1)) & 0x1);
170        break;
171      default:
172        throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern);
173    }
174    return intermediate == 0;
175  }
176
177  // Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both
178  // vertical and horizontal orders respectively.
179  private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) {
180    int penalty = 0;
181    int numSameBitCells = 0;
182    int prevBit = -1;
183    // Horizontal mode:
184    //   for (int i = 0; i < matrix.height(); ++i) {
185    //     for (int j = 0; j < matrix.width(); ++j) {
186    //       int bit = matrix.get(i, j);
187    // Vertical mode:
188    //   for (int i = 0; i < matrix.width(); ++i) {
189    //     for (int j = 0; j < matrix.height(); ++j) {
190    //       int bit = matrix.get(j, i);
191    int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth();
192    int jLimit = isHorizontal ? matrix.getWidth() : matrix.getHeight();
193    byte[][] array = matrix.getArray();
194    for (int i = 0; i < iLimit; ++i) {
195      for (int j = 0; j < jLimit; ++j) {
196        int bit = isHorizontal ? array[i][j] : array[j][i];
197        if (bit == prevBit) {
198          numSameBitCells += 1;
199          // Found five repetitive cells with the same color (bit).
200          // We'll give penalty of 3.
201          if (numSameBitCells == 5) {
202            penalty += 3;
203          } else if (numSameBitCells > 5) {
204            // After five repetitive cells, we'll add the penalty one
205            // by one.
206            penalty += 1;
207          }
208        } else {
209          numSameBitCells = 1;  // Include the cell itself.
210          prevBit = bit;
211        }
212      }
213      numSameBitCells = 0;  // Clear at each row/column.
214    }
215    return penalty;
216  }
217
218}