content: /*
   * Copyright 2017 The Android Things Samples Authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.example.androidthings.imageclassifier;
  
  import android.graphics.Bitmap;
  import android.graphics.Bitmap.Config;
  import android.graphics.BitmapFactory;
  import android.graphics.Canvas;
  import android.graphics.Matrix;
  import android.media.Image;
  import android.os.Environment;
  import android.util.Log;
  
  import junit.framework.Assert;
  
  import java.io.BufferedOutputStream;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.ByteBuffer;
  
  /**
   * Class that process an Image and extracts a Bitmap in a format appropriate for
   * the TensorFlow model.
   */
  public class ImagePreprocessor {
      private static final boolean SAVE_PREVIEW_BITMAP = false;
  
      private Bitmap rgbFrameBitmap;
      private Bitmap croppedBitmap;
  
      public ImagePreprocessor(int previewWidth, int previewHeight,
                               int croppedwidth, int croppedHeight) {
          this.croppedBitmap = Bitmap.createBitmap(croppedwidth, croppedHeight, Config.ARGB_8888);
          this.rgbFrameBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
      }
  
      public Bitmap preprocessImage(final Image image) {
          if (image == null) {
              return null;
          }
  
          Assert.assertEquals("Invalid size width", rgbFrameBitmap.getWidth(), image.getWidth());
          Assert.assertEquals("Invalid size height", rgbFrameBitmap.getHeight(), image.getHeight());
  
          if (croppedBitmap != null && rgbFrameBitmap != null) {
              ByteBuffer bb = image.getPlanes()[0].getBuffer();
              rgbFrameBitmap = BitmapFactory.decodeStream(new ByteBufferBackedInputStream(bb));
              cropAndRescaleBitmap(rgbFrameBitmap, croppedBitmap, 0);
          }
  
          image.close();
  
          // For debugging
          if (SAVE_PREVIEW_BITMAP) {
              saveBitmap(croppedBitmap);
          }
          return croppedBitmap;
      }
  
      private static class ByteBufferBackedInputStream extends InputStream {
  
          ByteBuffer buf;
  
          public ByteBufferBackedInputStream(ByteBuffer buf) {
              this.buf = buf;
          }
  
          public int read() throws IOException {
              if (!buf.hasRemaining()) {
                  return -1;
              }
              return buf.get() & 0xFF;
          }
  
          public int read(byte[] bytes, int off, int len)
                  throws IOException {
              if (!buf.hasRemaining()) {
                  return -1;
              }
  
              len = Math.min(len, buf.remaining());
              buf.get(bytes, off, len);
              return len;
          }
      }
  
      /**
       * Saves a Bitmap object to disk for analysis.
       *
       * @param bitmap The bitmap to save.
       */
      static void saveBitmap(final Bitmap bitmap) {
          final File file = new File(Environment.getExternalStoragePublicDirectory(
                  Environment.DIRECTORY_PICTURES), "tensorflow_preview.png");
          Log.d("ImageHelper", String.format("Saving %dx%d bitmap to %s.",
                  bitmap.getWidth(), bitmap.getHeight(), file.getAbsolutePath()));
  
          file.delete();
  
          try (FileOutputStream fs = new FileOutputStream(file);
               BufferedOutputStream out = new BufferedOutputStream(fs)) {
              bitmap.compress(Bitmap.CompressFormat.PNG, 99, out);
          } catch (Exception e) {
              Log.w("ImageHelper", "Could not save image for debugging", e);
          }
      }
  
      static void cropAndRescaleBitmap(final Bitmap src, final Bitmap dst,
                                       int sensorOrientation) {
          Assert.assertEquals(dst.getWidth(), dst.getHeight());
          final float minDim = Math.min(src.getWidth(), src.getHeight());
  
          final Matrix matrix = new Matrix();
  
          // We only want the center square out of the original rectangle.
          final float translateX = -Math.max(0, (src.getWidth() - minDim) / 2);
          final float translateY = -Math.max(0, (src.getHeight() - minDim) / 2);
          matrix.preTranslate(translateX, translateY);
  
          final float scaleFactor = dst.getHeight() / minDim;
          matrix.postScale(scaleFactor, scaleFactor);
  
          // Rotate around the center if necessary.
          if (sensorOrientation != 0) {
              matrix.postTranslate(-dst.getWidth() / 2.0f, -dst.getHeight() / 2.0f);
              matrix.postRotate(sensorOrientation);
              matrix.postTranslate(dst.getWidth() / 2.0f, dst.getHeight() / 2.0f);
          }
  
          final Canvas canvas = new Canvas(dst);
          canvas.drawBitmap(src, matrix, null);
      }
  }