| /* Copyright 2019 The TensorFlow Authors. All Rights Reserved. |
| |
| 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 org.tensorflow.lite.support.image; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.Color; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import org.tensorflow.lite.DataType; |
| import org.tensorflow.lite.support.tensorbuffer.TensorBuffer; |
| |
| /** |
| * Implements some stateless image conversion methods. |
| * |
| * <p>This class is an internal helper for {@link org.tensorflow.lite.support.image}. |
| */ |
| class ImageConversions { |
| |
| /** |
| * Converts a {@link TensorBuffer} that represents a RGB image to an ARGB_8888 Bitmap. |
| * |
| * <p>Data in buffer will be converted into integer to match the Bitmap API. |
| * |
| * @param buffer a RGB image. Its shape should be either (h, w, 3) or (1, h, w, 3) |
| * @throws IllegalArgumentException if the shape of buffer is neither (h, w, 3) nor (1, h, w, 3) |
| */ |
| static Bitmap convertRgbTensorBufferToBitmap(TensorBuffer buffer) { |
| int[] shape = buffer.getShape(); |
| ColorSpaceType rgb = ColorSpaceType.RGB; |
| rgb.assertShape(shape); |
| |
| int h = rgb.getHeight(shape); |
| int w = rgb.getWidth(shape); |
| Bitmap bitmap = Bitmap.createBitmap(w, h, rgb.toBitmapConfig()); |
| |
| // TODO(b/138904567): Find a way to avoid creating multiple intermediate buffers every time. |
| int[] intValues = new int[w * h]; |
| int[] rgbValues = buffer.getIntArray(); |
| for (int i = 0, j = 0; i < intValues.length; i++) { |
| int r = rgbValues[j++]; |
| int g = rgbValues[j++]; |
| int b = rgbValues[j++]; |
| intValues[i] = Color.rgb(r, g, b); |
| } |
| bitmap.setPixels(intValues, 0, w, 0, 0, w, h); |
| |
| return bitmap; |
| } |
| |
| /** |
| * Converts a {@link TensorBuffer} that represents a grayscale image to an ALPHA_8 Bitmap. |
| * |
| * <p>Data in buffer will be converted into integer to match the Bitmap API. |
| * |
| * @param buffer a grayscale image. Its shape should be either (h, w) or (1, h, w) |
| * @throws IllegalArgumentException if the shape of buffer is neither (h, w) nor (1, h, w, 1) |
| */ |
| static Bitmap convertGrayscaleTensorBufferToBitmap(TensorBuffer buffer) { |
| // Convert buffer into Uint8 as needed. |
| TensorBuffer uint8Buffer = |
| buffer.getDataType() == DataType.UINT8 |
| ? buffer |
| : TensorBuffer.createFrom(buffer, DataType.UINT8); |
| |
| int[] shape = uint8Buffer.getShape(); |
| ColorSpaceType grayscale = ColorSpaceType.GRAYSCALE; |
| grayscale.assertShape(shape); |
| |
| // Even though `Bitmap.createBitmap(int[] colors, int width, int height, Bitmap.Config config)` |
| // seems to work for internal Android testing framework, but it actually doesn't work for the |
| // real Android environment. |
| // |
| // The only reliable way to create an ALPHA_8 Bitmap is to use `copyPixelsFromBuffer()` to load |
| // the pixels from a ByteBuffer, and then use `copyPixelsToBuffer` to read out. |
| // Note: for ALPHA_8 Bitmap, methods such as, `setPixels()` and `getPixels()` do not work. |
| Bitmap bitmap = |
| Bitmap.createBitmap( |
| grayscale.getWidth(shape), grayscale.getHeight(shape), grayscale.toBitmapConfig()); |
| uint8Buffer.getBuffer().rewind(); |
| bitmap.copyPixelsFromBuffer(uint8Buffer.getBuffer()); |
| return bitmap; |
| } |
| |
| /** |
| * Converts an Image in a Bitmap to a TensorBuffer (3D Tensor: Width-Height-Channel) whose memory |
| * is already allocated, or could be dynamically allocated. |
| * |
| * @param bitmap The Bitmap object representing the image. Currently we only support ARGB_8888 |
| * config. |
| * @param buffer The destination of the conversion. Needs to be created in advance. If it's |
| * fixed-size, its flat size should be w*h*3. |
| * @throws IllegalArgumentException if the buffer is fixed-size, but the size doesn't match. |
| */ |
| static void convertBitmapToTensorBuffer(Bitmap bitmap, TensorBuffer buffer) { |
| int w = bitmap.getWidth(); |
| int h = bitmap.getHeight(); |
| int[] intValues = new int[w * h]; |
| bitmap.getPixels(intValues, 0, w, 0, 0, w, h); |
| // TODO(b/138904567): Find a way to avoid creating multiple intermediate buffers every time. |
| int flatSize = w * h * 3; |
| int[] shape = new int[] {h, w, 3}; |
| switch (buffer.getDataType()) { |
| case UINT8: |
| byte[] byteArr = new byte[w * h * 3]; |
| for (int i = 0, j = 0; i < intValues.length; i++) { |
| byteArr[j++] = (byte) ((intValues[i] >> 16) & 0xff); |
| byteArr[j++] = (byte) ((intValues[i] >> 8) & 0xff); |
| byteArr[j++] = (byte) (intValues[i] & 0xff); |
| } |
| ByteBuffer byteBuffer = ByteBuffer.allocateDirect(flatSize); |
| byteBuffer.order(ByteOrder.nativeOrder()); |
| byteBuffer.put(byteArr); |
| buffer.loadBuffer(byteBuffer, shape); |
| break; |
| case FLOAT32: |
| float[] floatArr = new float[w * h * 3]; |
| for (int i = 0, j = 0; i < intValues.length; i++) { |
| floatArr[j++] = (float) ((intValues[i] >> 16) & 0xff); |
| floatArr[j++] = (float) ((intValues[i] >> 8) & 0xff); |
| floatArr[j++] = (float) (intValues[i] & 0xff); |
| } |
| buffer.loadArray(floatArr, shape); |
| break; |
| default: |
| // Should never happen. |
| throw new IllegalStateException( |
| "The type of TensorBuffer, " + buffer.getBuffer() + ", is unsupported."); |
| } |
| } |
| |
| // Hide the constructor as the class is static. |
| private ImageConversions() {} |
| } |