From b91d4c934a664e06f4b5c41a1b198d56f0c8e1b9 Mon Sep 17 00:00:00 2001 From: Branden Archer Date: Sat, 21 May 2016 18:21:53 -0400 Subject: [PATCH 1/4] Generate barcode in an AsyncTask This change moves the generation of the barcode into its own async task. In addition, the size of the ImageView is used to determine the barcode size to use. There will be cases when the size of the ImageView will not be known when the barcode generation starts. This will be resolved in a future commit. --- .../card_locker/BarcodeImageWriterTask.java | 91 +++++++++++++++++++ .../card_locker/LoyaltyCardViewActivity.java | 56 ++---------- 2 files changed, 98 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java diff --git a/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java b/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java new file mode 100644 index 000000000..cd32e0f35 --- /dev/null +++ b/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java @@ -0,0 +1,91 @@ +package protect.card_locker; + +import android.graphics.Bitmap; +import android.os.AsyncTask; +import android.util.Log; +import android.widget.ImageView; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; + +import java.lang.ref.WeakReference; + +/** + * This task will generate a barcode and load it into an ImageView. + * Only a weak reference of the ImageView is kept, so this class will not + * prevent the ImageView from being garbage collected. + */ +class BarcodeImageWriterTask extends AsyncTask +{ + private static final String TAG = "LoyaltyCardLocker"; + + private final WeakReference imageViewReference; + private final String cardId; + private final BarcodeFormat format; + private final int imageHeight; + private final int imageWidth; + + public BarcodeImageWriterTask(ImageView imageView, String cardIdString, + BarcodeFormat barcodeFormat) + { + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<>(imageView); + + cardId = cardIdString; + format = barcodeFormat; + imageHeight = imageView.getHeight(); + imageWidth = imageView.getWidth(); + } + + public Bitmap doInBackground(Void... params) + { + MultiFormatWriter writer = new MultiFormatWriter(); + BitMatrix bitMatrix; + try + { + bitMatrix = writer.encode(cardId, format, imageWidth, imageHeight, null); + + final int WHITE = 0xFFFFFFFF; + final int BLACK = 0xFF000000; + + int bitMatrixWidth = bitMatrix.getWidth(); + int bitMatrixHeight = bitMatrix.getHeight(); + + int[] pixels = new int[bitMatrixWidth * bitMatrixHeight]; + + for (int y = 0; y < bitMatrixHeight; y++) + { + int offset = y * bitMatrixWidth; + for (int x = 0; x < bitMatrixWidth; x++) + { + int color = bitMatrix.get(x, y) ? BLACK : WHITE; + pixels[offset + x] = color; + } + } + Bitmap bitmap = Bitmap.createBitmap(bitMatrixWidth, bitMatrixHeight, + Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, bitMatrixWidth, 0, 0, bitMatrixWidth, bitMatrixHeight); + return bitmap; + } + catch (WriterException | IllegalArgumentException e) + { + Log.e(TAG, "Failed to generate barcode", e); + } + + return null; + } + + protected void onPostExecute(Bitmap result) + { + ImageView imageView = imageViewReference.get(); + if(imageView == null) + { + // The ImageView no longer exists, nothing to do + return; + } + + imageView.setImageBitmap(result); + } +} \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java index 015a76d7d..3cf30d3ae 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java @@ -2,7 +2,6 @@ package protect.card_locker; import android.content.Intent; -import android.graphics.Bitmap; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v7.app.ActionBar; @@ -17,9 +16,6 @@ import android.widget.EditText; import android.widget.ImageView; import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatWriter; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; @@ -149,52 +145,14 @@ public class LoyaltyCardViewActivity extends AppCompatActivity if(cardIdField.getText().length() > 0 && barcodeTypeField.getText().length() > 0) { - MultiFormatWriter writer = new MultiFormatWriter(); - BitMatrix result; - try - { - String formatString = barcodeTypeField.getText().toString(); - BarcodeFormat format = BarcodeFormat.valueOf(formatString); - if(format == null) - { - throw new IllegalArgumentException("Unrecognized barcode format: " + formatString); - } + String formatString = barcodeTypeField.getText().toString(); + final BarcodeFormat format = BarcodeFormat.valueOf(formatString); + final String cardIdString = cardIdField.getText().toString(); - int generateWidth = 100; - int generateHeight = 100; + new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute(); - String cardIdString = cardIdField.getText().toString(); - - Log.i(TAG, "Card: " + cardIdString); - - result = writer.encode(cardIdString, format, generateWidth, generateHeight, null); - - final int WHITE = 0xFFFFFFFF; - final int BLACK = 0xFF000000; - - int width = result.getWidth(); - int height = result.getHeight(); - int[] pixels = new int[width * height]; - for (int y = 0; y < height; y++) - { - int offset = y * width; - for (int x = 0; x < width; x++) - { - pixels[offset + x] = result.get(x, y) ? BLACK : WHITE; - } - } - Bitmap bitmap = Bitmap.createBitmap(width, height, - Bitmap.Config.ARGB_8888); - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); - barcodeImage.setImageBitmap(bitmap); - - barcodeIdLayout.setVisibility(View.VISIBLE); - barcodeImageLayout.setVisibility(View.VISIBLE); - } - catch (WriterException | IllegalArgumentException e) - { - Log.e(TAG, "Failed to generate barcode", e); - } + barcodeIdLayout.setVisibility(View.VISIBLE); + barcodeImageLayout.setVisibility(View.VISIBLE); } View.OnClickListener captureCallback = new View.OnClickListener() @@ -332,4 +290,4 @@ public class LoyaltyCardViewActivity extends AppCompatActivity } } } -} +} \ No newline at end of file From dc4a41088c01a365ab14d9ed5951f504c0fa3c12 Mon Sep 17 00:00:00 2001 From: Branden Archer Date: Sat, 21 May 2016 18:23:08 -0400 Subject: [PATCH 2/4] Generate barcode after ImageView size is known The size of the ImageView may not yet be known when the barcode generation is needed. If this is the case, wait until the final layout is complete then start the barcode generation. --- .../card_locker/LoyaltyCardViewActivity.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java index 3cf30d3ae..a11840d91 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java @@ -11,6 +11,7 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.ViewTreeObserver; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; @@ -149,7 +150,27 @@ public class LoyaltyCardViewActivity extends AppCompatActivity final BarcodeFormat format = BarcodeFormat.valueOf(formatString); final String cardIdString = cardIdField.getText().toString(); - new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute(); + if(barcodeImage.getHeight() == 0) + { + Log.d(TAG, "ImageView size is not known known at start, waiting for load"); + // The size of the ImageView is not yet available as it has not + // yet been drawn. Wait for it to be drawn so the size is available. + barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() + { + @Override + public void onGlobalLayout() + { + Log.d(TAG, "ImageView size now known"); + new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute(); + } + }); + } + else + { + Log.d(TAG, "ImageView size known known, creating barcode"); + new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute(); + } barcodeIdLayout.setVisibility(View.VISIBLE); barcodeImageLayout.setVisibility(View.VISIBLE); From cecec15762b4054fc53bf2c69bcafbac2f4ce9b1 Mon Sep 17 00:00:00 2001 From: Branden Archer Date: Sat, 21 May 2016 18:26:21 -0400 Subject: [PATCH 3/4] Scale barcodes to ImageView's size without filtering It turns out that the library used to create datamatrix barcodes returns the smallest image necessary to contain the barcode. That is, the size passed into the barcode writer. If the ImageView scales the tiny image itself into the full size it will use bi-linear filtering, which results in a blurry barcode. To avoid this, if scaling is needed do so without using filtering. --- .../card_locker/BarcodeImageWriterTask.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java b/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java index cd32e0f35..d71bee2ac 100644 --- a/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java +++ b/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java @@ -67,6 +67,23 @@ class BarcodeImageWriterTask extends AsyncTask Bitmap bitmap = Bitmap.createBitmap(bitMatrixWidth, bitMatrixHeight, Bitmap.Config.ARGB_8888); bitmap.setPixels(pixels, 0, bitMatrixWidth, 0, 0, bitMatrixWidth, bitMatrixHeight); + + // Determine if the image needs to be scaled. + // This is necessary because the datamatrix barcode generator + // ignores the requested size and returns the smallest image necessary + // to represent the barcode. If we let the ImageView scale the image + // it will use bi-linear filtering, which results in a blurry barcode. + // To avoid this, if scaling is needed do so without filtering. + + int heightScale = imageHeight / bitMatrixHeight; + int widthScale = imageWidth / bitMatrixHeight; + int scalingFactor = Math.min(heightScale, widthScale); + + if(scalingFactor > 1) + { + bitmap = Bitmap.createScaledBitmap(bitmap, bitMatrixWidth * scalingFactor, bitMatrixHeight * scalingFactor, false); + } + return bitmap; } catch (WriterException | IllegalArgumentException e) From b89c5eb91c259bd7b57a7af60b93c557429c656a Mon Sep 17 00:00:00 2001 From: Branden Archer Date: Sat, 21 May 2016 18:31:31 -0400 Subject: [PATCH 4/4] Change padding of card list on main screen The entire space for the ListView was not being used, which appeared odd. --- app/src/main/res/layout/content_main.xml | 4 ---- app/src/main/res/layout/loyalty_card_layout.xml | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index fab4bdcdd..56b5d76d1 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -5,10 +5,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingBottom="@dimen/activity_vertical_margin" - android:paddingLeft="@dimen/activity_horizontal_margin" - android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingTop="@dimen/activity_vertical_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="protect.card_locker.MainActivity" tools:showIn="@layout/main_activity"> diff --git a/app/src/main/res/layout/loyalty_card_layout.xml b/app/src/main/res/layout/loyalty_card_layout.xml index 1232c0517..cba2d38d9 100644 --- a/app/src/main/res/layout/loyalty_card_layout.xml +++ b/app/src/main/res/layout/loyalty_card_layout.xml @@ -1,11 +1,11 @@ @@ -18,7 +18,7 @@