com.drew.metadata.icc.IccDescriptor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metadata-extractor Show documentation
Show all versions of metadata-extractor Show documentation
Java library for extracting EXIF, IPTC, XMP, ICC and other metadata from image and video files.
/*
* Copyright 2002-2019 Drew Noakes and contributors
*
* 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.
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package com.drew.metadata.icc;
import com.drew.lang.ByteArrayReader;
import com.drew.lang.RandomAccessReader;
import com.drew.lang.annotations.NotNull;
import com.drew.lang.annotations.Nullable;
import com.drew.metadata.TagDescriptor;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import static com.drew.metadata.icc.IccDirectory.*;
/**
* @author Yuri Binev
* @author Drew Noakes https://drewnoakes.com
*/
@SuppressWarnings("WeakerAccess")
public class IccDescriptor extends TagDescriptor
{
public IccDescriptor(@NotNull IccDirectory directory)
{
super(directory);
}
@Override
public String getDescription(int tagType)
{
switch (tagType) {
case TAG_PROFILE_VERSION:
return getProfileVersionDescription();
case TAG_PROFILE_CLASS:
return getProfileClassDescription();
case TAG_PLATFORM:
return getPlatformDescription();
case TAG_RENDERING_INTENT:
return getRenderingIntentDescription();
}
if (tagType > 0x20202020 && tagType < 0x7a7a7a7a)
return getTagDataString(tagType);
return super.getDescription(tagType);
}
private static final int ICC_TAG_TYPE_TEXT = 0x74657874;
private static final int ICC_TAG_TYPE_DESC = 0x64657363;
private static final int ICC_TAG_TYPE_SIG = 0x73696720;
private static final int ICC_TAG_TYPE_MEAS = 0x6D656173;
private static final int ICC_TAG_TYPE_XYZ_ARRAY = 0x58595A20;
private static final int ICC_TAG_TYPE_MLUC = 0x6d6c7563;
private static final int ICC_TAG_TYPE_CURV = 0x63757276;
@Nullable
private String getTagDataString(int tagType)
{
try {
byte[] bytes = _directory.getByteArray(tagType);
if (bytes == null)
return _directory.getString(tagType);
RandomAccessReader reader = new ByteArrayReader(bytes);
int iccTagType = reader.getInt32(0);
switch (iccTagType) {
case ICC_TAG_TYPE_TEXT:
try {
return new String(bytes, 8, bytes.length - 8 - 1, "ASCII");
} catch (UnsupportedEncodingException ex) {
return new String(bytes, 8, bytes.length - 8 - 1);
}
case ICC_TAG_TYPE_DESC:
int stringLength = reader.getInt32(8);
return new String(bytes, 12, stringLength - 1);
case ICC_TAG_TYPE_SIG:
return IccReader.getStringFromInt32(reader.getInt32(8));
case ICC_TAG_TYPE_MEAS: {
int observerType = reader.getInt32(8);
float x = reader.getS15Fixed16(12);
float y = reader.getS15Fixed16(16);
float z = reader.getS15Fixed16(20);
int geometryType = reader.getInt32(24);
float flare = reader.getS15Fixed16(28);
int illuminantType = reader.getInt32(32);
String observerString;
switch (observerType) {
case 0:
observerString = "Unknown";
break;
case 1:
observerString = "1931 2\u00B0";
break;
case 2:
observerString = "1964 10\u00B0";
break;
default:
observerString = String.format("Unknown %d", observerType);
}
String geometryString;
switch (geometryType) {
case 0:
geometryString = "Unknown";
break;
case 1:
geometryString = "0/45 or 45/0";
break;
case 2:
geometryString = "0/d or d/0";
break;
default:
geometryString = String.format("Unknown %d", observerType);
}
String illuminantString;
switch (illuminantType) {
case 0:
illuminantString = "unknown";
break;
case 1:
illuminantString = "D50";
break;
case 2:
illuminantString = "D65";
break;
case 3:
illuminantString = "D93";
break;
case 4:
illuminantString = "F2";
break;
case 5:
illuminantString = "D55";
break;
case 6:
illuminantString = "A";
break;
case 7:
illuminantString = "Equi-Power (E)";
break;
case 8:
illuminantString = "F8";
break;
default:
illuminantString = String.format("Unknown %d", illuminantType);
break;
}
DecimalFormat format = new DecimalFormat("0.###");
return String.format("%s Observer, Backing (%s, %s, %s), Geometry %s, Flare %d%%, Illuminant %s",
observerString, format.format(x), format.format(y), format.format(z), geometryString, Math.round(flare * 100), illuminantString);
}
case ICC_TAG_TYPE_XYZ_ARRAY: {
StringBuilder res = new StringBuilder();
DecimalFormat format = new DecimalFormat("0.####");
int count = (bytes.length - 8) / 12;
for (int i = 0; i < count; i++) {
float x = reader.getS15Fixed16(8 + i * 12);
float y = reader.getS15Fixed16(8 + i * 12 + 4);
float z = reader.getS15Fixed16(8 + i * 12 + 8);
if (i > 0)
res.append(", ");
res.append("(").append(format.format(x)).append(", ").append(format.format(y)).append(", ").append(format.format(z)).append(")");
}
return res.toString();
}
case ICC_TAG_TYPE_MLUC: {
int int1 = reader.getInt32(8);
StringBuilder res = new StringBuilder();
res.append(int1);
//int int2 = reader.getInt32(12);
//System.err.format("int1: %d, int2: %d\n", int1, int2);
for (int i = 0; i < int1; i++) {
String str = IccReader.getStringFromInt32(reader.getInt32(16 + i * 12));
int len = reader.getInt32(16 + i * 12 + 4);
int ofs = reader.getInt32(16 + i * 12 + 8);
String name;
try {
name = new String(bytes, ofs, len, "UTF-16BE");
} catch (UnsupportedEncodingException ex) {
name = new String(bytes, ofs, len);
}
res.append(" ").append(str).append("(").append(name).append(")");
//System.err.format("% 3d: %s, len: %d, ofs: %d, \"%s\"\n", i, str, len,ofs,name);
}
return res.toString();
}
case ICC_TAG_TYPE_CURV: {
int num = reader.getInt32(8);
StringBuilder res = new StringBuilder();
for (int i = 0; i < num; i++) {
if (i != 0)
res.append(", ");
res.append(formatDoubleAsString(((float)reader.getUInt16(12 + i * 2)) / 65535.0, 7, false));
//res+=String.format("%1.7g",Math.round(((float)iccReader.getInt16(b,12+i*2))/0.065535)/1E7);
}
return res.toString();
}
default:
return String.format("%s (0x%08X): %d bytes", IccReader.getStringFromInt32(iccTagType), iccTagType, bytes.length);
}
} catch (IOException e) {
// TODO decode these values during IccReader.extract so we can report any errors at that time
// It is convention to return null if a description cannot be formulated.
// If an error is to be reported, it should be done during the extraction process.
return null;
}
}
@NotNull
public static String formatDoubleAsString(double value, int precision, boolean zeroes)
{
if (precision < 1)
return "" + Math.round(value);
long intPart = Math.abs((long)value);
long rest = (int)Math.round((Math.abs(value) - intPart) * Math.pow(10, precision));
long restKept = rest;
String res = "";
byte cour;
for (int i = precision; i > 0; i--) {
cour = (byte)(Math.abs(rest % 10));
rest /= 10;
if (res.length() > 0 || zeroes || cour != 0 || i == 1)
res = cour + res;
}
intPart += rest;
boolean isNegative = ((value < 0) && (intPart != 0 || restKept != 0));
return (isNegative ? "-" : "") + intPart + "." + res;
}
@Nullable
private String getRenderingIntentDescription()
{
return getIndexedDescription(TAG_RENDERING_INTENT,
"Perceptual",
"Media-Relative Colorimetric",
"Saturation",
"ICC-Absolute Colorimetric");
}
@Nullable
private String getPlatformDescription()
{
String str = _directory.getString(TAG_PLATFORM);
if (str==null)
return null;
// Because Java doesn't allow switching on string values, create an integer from the first four chars
// and switch on that instead.
int i;
try {
i = getInt32FromString(str);
} catch (IOException e) {
return str;
}
switch (i) {
case 0x4150504C: // "APPL"
return "Apple Computer, Inc.";
case 0x4D534654: // "MSFT"
return "Microsoft Corporation";
case 0x53474920:
return "Silicon Graphics, Inc.";
case 0x53554E57:
return "Sun Microsystems, Inc.";
case 0x54474E54:
return "Taligent, Inc.";
default:
return String.format("Unknown (%s)", str);
}
}
@Nullable
private String getProfileClassDescription()
{
String str = _directory.getString(TAG_PROFILE_CLASS);
if (str==null)
return null;
// Because Java doesn't allow switching on string values, create an integer from the first four chars
// and switch on that instead.
int i;
try {
i = getInt32FromString(str);
} catch (IOException e) {
return str;
}
switch (i) {
case 0x73636E72:
return "Input Device";
case 0x6D6E7472: // mntr
return "Display Device";
case 0x70727472:
return "Output Device";
case 0x6C696E6B:
return "DeviceLink";
case 0x73706163:
return "ColorSpace Conversion";
case 0x61627374:
return "Abstract";
case 0x6E6D636C:
return "Named Color";
default:
return String.format("Unknown (%s)", str);
}
}
@Nullable
private String getProfileVersionDescription()
{
Integer value = _directory.getInteger(TAG_PROFILE_VERSION);
if (value == null)
return null;
int m = (value & 0xFF000000) >> 24;
int r = (value & 0x00F00000) >> 20;
int R = (value & 0x000F0000) >> 16;
return String.format("%d.%d.%d", m, r, R);
}
private static int getInt32FromString(@NotNull String string) throws IOException
{
byte[] bytes = string.getBytes();
return new ByteArrayReader(bytes).getInt32(0);
}
}