/* * Universal Media Server, for streaming any media to DLNA * compatible renderers based on the http://www.ps3mediaserver.org. * Copyright (C) 2012 UMS developers. * * This program is a free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License only. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.pms.image; import java.io.Serializable; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.drew.metadata.Metadata; import com.drew.metadata.jpeg.JpegComponent; import com.drew.metadata.jpeg.JpegDirectory; /** * This class is used to hold a subsampling J:a:b notation value. Use * {@link #toString()} to get a formatted {@link String}. * <p> * {@link #calculateJPEGSubsampling(Metadata)} and * {@link #calculateJPEGSubsampling(JpegComponent[], int, int)} are factory * methods to create {@link JPEGSubsamplingNotation} instances from the * {@link Metadata} of a JPEG image or for from a {@link JpegComponent} * respectively. * * @author Nadahar */ @SuppressWarnings("serial") public class JPEGSubsamplingNotation implements Serializable { private static final Logger LOGGER = LoggerFactory.getLogger(JPEGSubsamplingNotation.class); private final double j; private final double a; private final double b; /** * Creates a new {@link JPEGSubsamplingNotation} with the given factors. * * @param j the {@code J} factor. * @param a the {@code a} factor. * @param b the {@code b} factor. */ public JPEGSubsamplingNotation(int j, int a, int b) { this.j = j; this.a = a; this.b = b; } public JPEGSubsamplingNotation(double j, double a, double b) { this.j = j; this.a = a; this.b = b; } // Internal constructor protected JPEGSubsamplingNotation(double[] factors) { j = factors[0]; a = factors[1]; b = factors[2]; } /** * Calculates the J:a:b chroma subsampling notation from a {@link Metadata} * instance generated from a JPEG image. If the non-luminance components * have different subsampling values or the calculation is impossible, * {@link Double#NaN} will be returned for all factors. * * @param metadata the {@link Metadata} instance from which to calculate. * @return A {@link JPEGSubsamplingNotation} with the result. */ public static JPEGSubsamplingNotation calculateJPEGSubsampling(Metadata metadata) { if (metadata == null) { throw new NullPointerException("metadata cannot be null"); } JpegDirectory directory = metadata.getFirstDirectoryOfType(JpegDirectory.class); if (directory == null) { return new JPEGSubsamplingNotation(Double.NaN, Double.NaN, Double.NaN); } return calculateJPEGSubsampling(directory); } /** * Calculates the J:a:b chroma subsampling notation from a * {@link JpegDirectory} instance generated from a JPEG image. If the * non-luminance components have different subsampling values or the * calculation is impossible, {@link Double#NaN} will be returned for all * factors. * * @param directory the {@link JpegDirectory} instance from which to * calculate. * @return A {@link JPEGSubsamplingNotation} with the result. */ public static JPEGSubsamplingNotation calculateJPEGSubsampling(JpegDirectory directory) { if (directory == null) { throw new NullPointerException("directory cannot be null"); } if ( directory.getInteger(JpegDirectory.TAG_NUMBER_OF_COMPONENTS) == null || directory.getInteger(JpegDirectory.TAG_NUMBER_OF_COMPONENTS).intValue() == 0 ) { return new JPEGSubsamplingNotation(Double.NaN, Double.NaN, Double.NaN); } int numComponents = directory.getInteger(JpegDirectory.TAG_NUMBER_OF_COMPONENTS).intValue(); int luminanceIdx = -1; JpegComponent[] components = new JpegComponent[numComponents]; for (int i = 0; i < numComponents; i++) { components[i] = directory.getComponent(i); if (components[i].getComponentId() == 1) { luminanceIdx = i; } } if (luminanceIdx < 0) { return new JPEGSubsamplingNotation(Double.NaN, Double.NaN, Double.NaN); } JPEGSubsamplingNotation result = null; for (int i = 0; i < numComponents; i++) { if (i != luminanceIdx) { JPEGSubsamplingNotation componentResult = calculateJPEGSubsampling(components, luminanceIdx, i); if (result == null) { result = componentResult; } else { if (!result.equals(componentResult)) { LOGGER.trace( "Components {} and {} have mismatching chroma subsampling {} and " + "{}. Unable to determine an overall chroma subsampling notation", components[i - 1].getComponentName(), components[i].getComponentName(), result, componentResult ); return new JPEGSubsamplingNotation(Double.NaN, Double.NaN, Double.NaN); } } } } return result != null ? result : new JPEGSubsamplingNotation(Double.NaN, Double.NaN, Double.NaN); } /** * Calculates the J:a:b subsampling notation values for a given * {@link JpegComponent} indicated by {@code componentIdx}. * {@code luminanceIdx} is used to identify the luminance component which is * needed as a reference. * * @param components the array of {@link JpegComponent} to calculate from. * @param luminanceIdx the index of the luminance component within * {@code components}. * @param componentIdx the index of the component within {@code components} * for which to calculate the subsampling notation values. * @return A {@link JPEGSubsamplingNotation} with the result. */ public static JPEGSubsamplingNotation calculateJPEGSubsampling( JpegComponent[] components, int luminanceIdx, int componentIdx ) { double[] result = new double[3]; result[0] = 4; int hMax = 0; int vMax = 0; for (int i = 0; i < components.length; i++) { hMax = Math.max(hMax, components[i].getHorizontalSamplingFactor()); vMax = Math.max(vMax, components[i].getVerticalSamplingFactor()); } double[] h = new double[components.length]; double[] v = new double[components.length]; for (int i = 0; i < components.length; i++) { h[i] = (double) components[i].getHorizontalSamplingFactor() / hMax; v[i] = (double) components[i].getVerticalSamplingFactor() / vMax; } result[1] = 4 * h[componentIdx]; double cHeight = 2 * v[componentIdx]; result[2] = Double.NaN; if (cHeight == 2) { result[2] = result[1]; } else if (cHeight == 1) { result[2] = 0; } else if (cHeight < 1) { result[2] = 1; } return new JPEGSubsamplingNotation(result); } /** * @return The J value in J:a:b. */ public double getJ() { return j; } /** * @return The a value in J:a:b. */ public double getA() { return a; } /** * @return The b value in J:a:b. */ public double getB() { return b; } @Override public String toString() { if ( (Double.isNaN(j) || Double.isInfinite(j)) && (Double.isNaN(a) || Double.isInfinite(a)) && (Double.isNaN(b) || Double.isInfinite(b)) ) { return "N/A"; } DecimalFormat df = new DecimalFormat("#.##", new DecimalFormatSymbols(Locale.ROOT)); return String.format( Locale.ROOT, "%s:%s:%s", j == (long) j ? (long) j : df.format(j), a == (long) a ? (long) a : df.format(a), b == (long) b ? (long) b : df.format(b) ); } @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(a); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(b); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(j); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof JPEGSubsamplingNotation)) { return false; } JPEGSubsamplingNotation other = (JPEGSubsamplingNotation) obj; if (Double.doubleToLongBits(a) != Double.doubleToLongBits(other.a)) { return false; } if (Double.doubleToLongBits(b) != Double.doubleToLongBits(other.b)) { return false; } if (Double.doubleToLongBits(j) != Double.doubleToLongBits(other.j)) { return false; } return true; } }