blob: 620f5c0ed26252339b23a0fd8497442c17d04d81 [file] [log] [blame] [edit]
/*
* Copyright (c) 2014-2021 by Wen Yu
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* or any later version.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
*
* Change History - most recent changes go on top of previous changes
*
* ThumbnailResource.java
* <p>
* Adobe thumbnail resource wrapper
*
* Who Date Description
* ==== ========= ===================================================
* WY 09Apr2016 Added new constructor
* WY 14Apr2015 Fixed a bug with super() call, changed data to null
* WY 14Apr2015 Added new constructor
* WY 13Apr2015 Initial creation
*/
package pixy.meta.adobe;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.graphics.Bitmap;
import pixy.io.IOUtils;
import pixy.meta.Thumbnail;
import pixy.util.ArrayUtils;
import pixy.util.MetadataUtils;
public class ThumbnailResource extends _8BIM {
// Check to make sure id is either ImageResourceID.THUMBNAIL_RESOURCE_PS4
// or ImageResourceID.THUMBNAIL_RESOURCE_PS5
private static ImageResourceID validateID(ImageResourceID id) {
if(id != ImageResourceID.THUMBNAIL_RESOURCE_PS4 && id != ImageResourceID.THUMBNAIL_RESOURCE_PS5)
throw new IllegalArgumentException("Unsupported thumbnail ImageResourceID: " + id);
return id;
}
// Fields
private int width;
private int height;
//Padded row bytes = (width * bits per pixel + 31) / 32 * 4.
private int paddedRowBytes;
// Total size = widthbytes * height * planes
private int totalSize;
// Size after compression. Used for consistency check.
private int compressedSize;
// Bits per pixel. = 24
private int bitsPerPixel;
// Number of planes. = 1
private int numOfPlanes;
private ImageResourceID id;
private int dataType;
// Thumbnail
private IRBThumbnail thumbnail = new IRBThumbnail();
public ThumbnailResource(Bitmap thumbnail) {
this("THUMBNAIL_RESOURCE", thumbnail);
}
public ThumbnailResource(String name, Bitmap thumbnail) {
super(ImageResourceID.THUMBNAIL_RESOURCE_PS5, name, null);
try {
this.thumbnail = createThumbnail(thumbnail);
} catch (IOException e) {
throw new RuntimeException("Unable to create IRBThumbnail from Bitmap");
}
}
// id is either ImageResourceID.THUMBNAIL_RESOURCE_PS4 or ImageResourceID.THUMBNAIL_RESOURCE_PS5
public ThumbnailResource(ImageResourceID id, int dataType, int width, int height, byte[] thumbnailData) {
super(validateID(id), "THUMBNAIL_RESOURCE", null);
// Initialize fields
this.id = id;
this.dataType = dataType;
/** Sometimes, we don't have information about width and height */
this.width = (width > 0)? width : 0;
this.height = (height > 0)? height : 0;
// paddedRowBytes = (width * bitsPerPixel + 31) / 32 * 4.
// totalSize = paddedRowBytes * height * numOfPlanes
this.paddedRowBytes = (width * 24 + 31)/32 * 4;
this.totalSize = paddedRowBytes * height * numOfPlanes;
this.compressedSize = thumbnailData.length;
this.bitsPerPixel = 24;
this.numOfPlanes = 1;
setThumbnailImage(id, dataType, width, height, totalSize, thumbnailData);
}
// id is either ImageResourceID.THUMBNAIL_RESOURCE_PS4 or ImageResourceID.THUMBNAIL_RESOURCE_PS5
public ThumbnailResource(ImageResourceID id, byte[] data) {
super(validateID(id), "THUMBNAIL_RESOURCE", data);
this.id = id;
read();
}
public ThumbnailResource(ImageResourceID id,Thumbnail thumbnail) {
this(id, thumbnail.getDataType(), thumbnail.getWidth(), thumbnail.getHeight(), thumbnail.getCompressedImage());
}
private IRBThumbnail createThumbnail(Bitmap thumbnail) throws IOException {
// Create memory buffer to write data
ByteArrayOutputStream bout = new ByteArrayOutputStream();
// Compress the thumbnail
try {
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, bout);
} catch (Exception e) {
e.printStackTrace();
}
byte[] data = bout.toByteArray();
this.id = ImageResourceID.THUMBNAIL_RESOURCE_PS5;
// Write thumbnail dimension
this.width = thumbnail.getWidth();
this.height = thumbnail.getHeight();
// Padded row bytes = (width * bits per pixel + 31) / 32 * 4.
this.bitsPerPixel = 24;
this.numOfPlanes = 1;
this.paddedRowBytes = (width*bitsPerPixel + 31)/32*4;
// Total size = widthbytes * height * planes
this.totalSize = paddedRowBytes*height*numOfPlanes;
// Size after compression. Used for consistency check.
this.compressedSize = data.length;
this.dataType = Thumbnail.DATA_TYPE_KJpegRGB;
return new IRBThumbnail(width, height, dataType, data);
}
public int getBitsPerPixel() {
return bitsPerPixel;
}
public int getCompressedSize() {
return compressedSize;
}
public int getDataType() {
return dataType;
}
public int getHeight() {
return height;
}
public int getNumOfPlanes() {
return numOfPlanes;
}
public int getPaddedRowBytes() {
return paddedRowBytes;
}
public ImageResourceID getResouceID() {
return id;
}
public IRBThumbnail getThumbnail() {
return new IRBThumbnail(thumbnail);
}
public int getTotalSize() {
return totalSize;
}
public int getWidth() {
return width;
}
private void setThumbnailImage(ImageResourceID id, int dataType, int width, int height, int totalSize, byte[] thumbnailData) {
// JFIF data in RGB format. For resource ID 1033 (0x0409) the data is in BGR format.
if(dataType == Thumbnail.DATA_TYPE_KJpegRGB) {
thumbnail.setImage(width, height, dataType, thumbnailData);
} else if(dataType == Thumbnail.DATA_TYPE_KRawRGB) {
// kRawRGB - NOT tested yet!
int[] colors = null;
if(id == ImageResourceID.THUMBNAIL_RESOURCE_PS4)
colors = MetadataUtils.bgr2ARGB(thumbnailData);
else if(id == ImageResourceID.THUMBNAIL_RESOURCE_PS5)
colors = MetadataUtils.toARGB(thumbnailData);
thumbnail.setImage(Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888));
} else
throw new UnsupportedOperationException("Unsupported IRB thumbnail data type: " + dataType);
}
private void read() {
this.dataType = IOUtils.readIntMM(data, 0); //1 = kJpegRGB. Also supports kRawRGB (0).
this.width = IOUtils.readIntMM(data, 4);
this.height = IOUtils.readIntMM(data, 8);
// Padded row bytes = (width * bits per pixel + 31) / 32 * 4.
this.paddedRowBytes = IOUtils.readIntMM(data, 12);
// Total size = widthbytes * height * planes
this.totalSize = IOUtils.readIntMM(data, 16);
// Size after compression. Used for consistency check.
this.compressedSize = IOUtils.readIntMM(data, 20);
this.bitsPerPixel = IOUtils.readShortMM(data, 24); // Bits per pixel. = 24
this.numOfPlanes = IOUtils.readShortMM(data, 26); // Number of planes. = 1
byte[] thumbnailData = null;
if(dataType == Thumbnail.DATA_TYPE_KJpegRGB)
thumbnailData = ArrayUtils.subArray(data, 28, compressedSize);
else if(dataType == Thumbnail.DATA_TYPE_KRawRGB)
thumbnailData = ArrayUtils.subArray(data, 28, totalSize);
setThumbnailImage(id, dataType, width, height, totalSize, thumbnailData);
}
public void write(OutputStream os) throws IOException {
if(data == null) {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
thumbnail.write(bout);
byte[] compressedData = bout.toByteArray();
bout.reset();
// Write thumbnail format
IOUtils.writeIntMM(bout, dataType);
IOUtils.writeIntMM(bout, width);
IOUtils.writeIntMM(bout, height);
IOUtils.writeIntMM(bout, paddedRowBytes);
// Total size = widthbytes * height * planes
IOUtils.writeIntMM(bout, totalSize);
// Size after compression. Used for consistency check.
IOUtils.writeIntMM(bout, compressedData.length);
IOUtils.writeShortMM(bout, bitsPerPixel);
IOUtils.writeShortMM(bout, numOfPlanes);
bout.write(compressedData);
data = bout.toByteArray();
size = data.length;
}
super.write(os);
}
}