blob: 7e21e6ad428f27acbf59f9b156e6d3ef4f9759b5 [file] [log] [blame]
/* Copyright 2021 Google LLC. 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 com.google.android.odml.image;
import android.graphics.Rect;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.odml.image.annotation.KeepForSdk;
import java.io.Closeable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Wraps image data for on-device machine learning (ODML) usages.
*
* <p>{@link MlImage} is designed to be an immutable image container, which could be shared
* cross-platforms, among different Google ODML frameworks(TFLite Support, MLKit).
*
* <p>It is a common abstraction image that could help to chain different frameworks that adapts
* {@link MlImage} together.
*
* <p>To construct an {@link MlImage}, use the provided builders:
*
* <ul>
* <li>{@link ByteBufferMlImageBuilder}
* <li>{@link BitmapMlImageBuilder}
* <li>{@link MediaMlImageBuilder}
* </ul>
*
* <p>{@link MlImage} uses reference counting to maintain internal storage. When it is created the
* reference count is 1. Developer can call {@link #close()} to reduce reference count to release
* internal storage earlier, otherwise Java garbage collection will release the storage eventually.
*
* <p>To extract concrete image, first check {@link StorageType} and then use the provided
* extractors:
*
* <ul>
* <li>{@link ByteBufferExtractor}
* <li>{@link BitmapExtractor}
* <li>{@link MediaImageExtractor}
* </ul>
*
* In future release, {@link MlImage} will support internal conversion(e.g. Bitmap -> ByteBuffer)
* and multiple storages.
*/
public class MlImage implements Closeable {
/** Specifies the image format of an image. */
@IntDef({
IMAGE_FORMAT_UNKNOWN,
IMAGE_FORMAT_RGBA,
IMAGE_FORMAT_RGB,
IMAGE_FORMAT_NV12,
IMAGE_FORMAT_NV21,
IMAGE_FORMAT_YV12,
IMAGE_FORMAT_YV21,
IMAGE_FORMAT_YUV_420_888,
IMAGE_FORMAT_ALPHA,
IMAGE_FORMAT_JPEG,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ImageFormat {}
public static final int IMAGE_FORMAT_UNKNOWN = 0;
public static final int IMAGE_FORMAT_RGBA = 1;
public static final int IMAGE_FORMAT_RGB = 2;
public static final int IMAGE_FORMAT_NV12 = 3;
public static final int IMAGE_FORMAT_NV21 = 4;
public static final int IMAGE_FORMAT_YV12 = 5;
public static final int IMAGE_FORMAT_YV21 = 6;
public static final int IMAGE_FORMAT_YUV_420_888 = 7;
public static final int IMAGE_FORMAT_ALPHA = 8;
public static final int IMAGE_FORMAT_JPEG = 9;
/** Specifies the image container type. Would be useful for choosing extractors. */
@IntDef({
STORAGE_TYPE_BITMAP,
STORAGE_TYPE_BYTEBUFFER,
STORAGE_TYPE_MEDIA_IMAGE,
STORAGE_TYPE_IMAGE_PROXY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface StorageType {}
public static final int STORAGE_TYPE_BITMAP = 1;
public static final int STORAGE_TYPE_BYTEBUFFER = 2;
public static final int STORAGE_TYPE_MEDIA_IMAGE = 3;
public static final int STORAGE_TYPE_IMAGE_PROXY = 4;
/**
* Returns a list of supported image properties for this {@link MlImage}.
*
* <p>Currently {@link MlImage} only support single storage type so the size of return list will
* always be 1.
*
* @see ImageProperties
*/
public List<ImageProperties> getContainedImageProperties() {
return Collections.singletonList(getContainer().getImageProperties());
}
/** Returns the rotation value attached to the image. Rotation value will be 0, 90, 180, 270. */
public int getRotation() {
return rotation;
}
/** Returns the timestamp attached to the image. */
long getTimestamp() {
return timestamp;
}
/** Returns the width of the image. */
public int getWidth() {
return width;
}
/** Returns the height of the image. */
public int getHeight() {
return height;
}
/** Returns the region-of-interest rectangle attached to the image. */
Rect getRoi() {
Rect result = new Rect();
result.set(roi);
return result;
}
/**
* Acquires a reference on this {@link MlImage}. This will increase the reference count by 1.
*/
private synchronized void acquire() {
referenceCount += 1;
}
/**
* Removes a reference that was previously acquired or init.
*
* <p>When {@link MlImage} is created, it has 1 reference count.
*
* <p>When the reference count becomes 0, it will release the resource under the hood.
*/
@Override
// TODO(b/189767728): Create an internal flag to indicate image is closed, or use referenceCount
public synchronized void close() {
referenceCount -= 1;
if (referenceCount == 0) {
for (ImageContainer imageContainer : containerMap.values()) {
imageContainer.close();
}
}
}
/**
* Advanced API access for {@link MlImage}.
*
* <p>These APIs are useful for other infrastructures, for example, acquiring extra reference
* count for {@link MlImage}. However, an App developer should avoid using the following APIs.
*
* <p>APIs inside are treated as internal APIs which are subject to change.
*/
public static final class Internal {
/**
* Acquires a reference on this {@link MlImage}. This will increase the reference count
* by 1.
*
* <p>This method is more useful for image consumer to acquire a reference so image resource
* will not be closed accidentally. As image creator, normal developer doesn't need to call
* this method.
*
* <p>The reference count is 1 when {@link MlImage} is created. Developer can call {@link
* #close()} to indicate it doesn't need this {@link MlImage} anymore.
*
* @see #close()
*/
public void acquire() {
image.acquire();
}
private final MlImage image;
// Only MlImage creates the internal helper.
private Internal(MlImage image) {
this.image = image;
}
}
/** Gets {@link Internal} object which contains internal APIs. */
public Internal getInternal() {
return new Internal(this);
}
private final Map<ImageProperties, ImageContainer> containerMap;
private final int rotation;
private final Rect roi;
private final long timestamp;
private final int width;
private final int height;
private int referenceCount;
/** Constructs an {@link MlImage} with a built container. */
@KeepForSdk
MlImage(ImageContainer container, int rotation, Rect roi, long timestamp, int width,
int height) {
this.containerMap = new HashMap<>();
containerMap.put(container.getImageProperties(), container);
this.rotation = rotation;
this.roi = new Rect();
this.roi.set(roi);
this.timestamp = timestamp;
this.width = width;
this.height = height;
this.referenceCount = 1;
}
/**
* Gets one available container.
*
* @return the current container.
*/
@KeepForSdk
ImageContainer getContainer() {
// According to the design, in the future we will support multiple containers in one image.
// Currently just return the original container.
// TODO(b/182443927): Cache multiple containers in MlImage.
return containerMap.values().iterator().next();
}
/**
* Gets container from required {@code storageType}. Returns {@code null} if not existed.
*
* <p>If there are multiple containers with required {@code storageType}, returns the first one.
*/
@Nullable
@KeepForSdk
ImageContainer getContainer(@StorageType int storageType) {
for (Entry<ImageProperties, ImageContainer> entry : containerMap.entrySet()) {
if (entry.getKey().getStorageType() == storageType) {
return entry.getValue();
}
}
return null;
}
/**
* Gets container from required {@code imageProperties}. Returns {@code null} if non existed.
*/
@Nullable
@KeepForSdk
ImageContainer getContainer(ImageProperties imageProperties) {
return containerMap.get(imageProperties);
}
/** Adds a new container if it doesn't exist. Returns {@code true} if it succeeds. */
boolean addContainer(ImageContainer container) {
ImageProperties imageProperties = container.getImageProperties();
if (containerMap.containsKey(imageProperties)) {
return false;
}
containerMap.put(imageProperties, container);
return true;
}
/**
* Validates rotation values for builders. Only supports 0, 90, 180, 270.
*
* @throws IllegalArgumentException if the rotation value is invalid.
*/
static void validateRotation(int rotation) {
if (rotation != 0 && rotation != 90 && rotation != 180 && rotation != 270) {
throw new IllegalArgumentException(
"Rotation value " + rotation + " is not valid. Use only 0, 90, 180 or 270.");
}
}
}