use of net.pms.dlna.DLNAImage in project UniversalMediaServer by UniversalMediaServer.
the class ImagesUtil method transcodeImage.
/**
* Converts and scales an image in one operation. Scaling can be with or
* without padding. Preserves aspect ratio and rotates/flips the image
* according to Exif orientation. Format support is limited to that of
* {@link ImageIO}. Only one of the three input arguments may be used in any
* given call. Note that {@code outputProfile} overrides
* {@code outputFormat}.
* <p>
* <b> This method consumes and closes {@code inputStream}. </b>
*
* @param inputByteArray the source image in a supported format.
* @param inputImage the source {@link Image}.
* @param inputStream the source image in a supported format.
* @param width the new width or 0 to disable scaling.
* @param height the new height or 0 to disable scaling.
* @param scaleType the {@link ScaleType} to use when scaling.
* @param outputFormat the {@link ImageFormat} to convert to or
* {@link ImageFormat#SOURCE} to preserve source format.
* Overridden by {@code outputProfile}.
* @param outputProfile the {@link DLNAImageProfile} to convert to. This
* overrides {@code outputFormat}.
* @param dlnaCompliant whether or not the output image should be restricted
* to DLNA compliance. This also means that the output can be
* safely cast to {@link DLNAImage}.
* @param dlnaThumbnail whether or not the output image should be restricted
* to DLNA thumbnail compliance. This also means that the output
* can be safely cast to {@link DLNAThumbnail}.
* @param padToSize whether padding should be used if source aspect doesn't
* match target aspect.
* @return The scaled and/or converted image or {@code null} if the source
* is {@code null}.
* @throws IOException if the operation fails.
*/
protected static Image transcodeImage(byte[] inputByteArray, Image inputImage, InputStream inputStream, int width, int height, ScaleType scaleType, ImageFormat outputFormat, DLNAImageProfile outputProfile, boolean dlnaCompliant, boolean dlnaThumbnail, boolean padToSize) throws IOException {
if (inputByteArray == null && inputStream == null && inputImage == null) {
return null;
}
if ((inputByteArray != null & inputImage != null) || (inputByteArray != null & inputStream != null) || (inputImage != null & inputStream != null)) {
throw new IllegalArgumentException("Use either inputByteArray, inputImage or inputStream");
}
boolean trace = LOGGER.isTraceEnabled();
if (trace) {
StringBuilder sb = new StringBuilder();
if (scaleType != null) {
sb.append("ScaleType = ").append(scaleType);
}
if (width > 0 && height > 0) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append("Width = ").append(width).append(", Height = ").append(height);
}
if (sb.length() > 0) {
sb.append(", ");
}
sb.append("PadToSize = ").append(padToSize ? "True" : "False");
LOGGER.trace("Converting {} image source to {} format and type {} using the following parameters: {}", inputByteArray != null ? "byte array" : inputImage != null ? "Image" : "input stream", outputProfile != null ? outputProfile : outputFormat, dlnaThumbnail ? "DLNAThumbnail" : dlnaCompliant ? "DLNAImage" : "Image", sb);
}
ImageIO.setUseCache(false);
dlnaCompliant = dlnaCompliant || dlnaThumbnail;
if (inputImage != null) {
inputByteArray = inputImage.getBytes(false);
} else if (inputStream != null) {
inputByteArray = ImagesUtil.toByteArray(inputStream);
}
// outputProfile overrides outputFormat
if (outputProfile != null) {
if (dlnaThumbnail && outputProfile.equals(DLNAImageProfile.GIF_LRG)) {
outputProfile = DLNAImageProfile.JPEG_LRG;
}
// Default to correct ScaleType for the profile
if (scaleType == null) {
if (DLNAImageProfile.JPEG_RES_H_V.equals(outputProfile)) {
scaleType = ScaleType.EXACT;
} else {
scaleType = ScaleType.MAX;
}
}
outputFormat = ImageFormat.toImageFormat(outputProfile);
width = width > 0 ? width : outputProfile.getMaxWidth();
height = height > 0 ? height : outputProfile.getMaxHeight();
} else if (scaleType == null) {
scaleType = ScaleType.MAX;
}
ImageReaderResult inputResult;
try {
inputResult = ImageIOTools.read(new ByteArrayInputStream(inputByteArray));
} catch (IIOException e) {
throw new UnknownFormatException("Unable to read image format", e);
}
if (inputResult.bufferedImage == null || inputResult.imageFormat == null) {
// ImageIO doesn't support the image format
throw new UnknownFormatException("Failed to transform image because the source format is unknown");
}
if (outputFormat == null || outputFormat == ImageFormat.SOURCE) {
outputFormat = inputResult.imageFormat;
}
BufferedImage bufferedImage = inputResult.bufferedImage;
boolean reencode = false;
if (outputProfile == null && dlnaCompliant) {
// if the source image has alpha and JPEG if not.
switch(outputFormat) {
case GIF:
if (dlnaThumbnail) {
outputFormat = ImageFormat.JPEG;
}
break;
case JPEG:
case PNG:
break;
default:
if (bufferedImage.getColorModel().hasAlpha()) {
outputFormat = ImageFormat.PNG;
} else {
outputFormat = ImageFormat.JPEG;
}
}
}
Metadata metadata = null;
ExifOrientation orientation;
if (inputImage != null && inputImage.getImageInfo() != null) {
orientation = inputImage.getImageInfo().getExifOrientation();
} else {
try {
metadata = getMetadata(inputByteArray, inputResult.imageFormat);
} catch (IOException | ImageProcessingException e) {
LOGGER.error("Failed to read input image metadata: {}", e.getMessage());
LOGGER.trace("", e);
metadata = new Metadata();
}
if (metadata == null) {
metadata = new Metadata();
}
orientation = parseExifOrientation(metadata);
}
if (orientation != ExifOrientation.TOP_LEFT) {
// Rotate the image before doing all the other checks
BufferedImage oldBufferedImage = bufferedImage;
bufferedImage = Thumbnails.of(bufferedImage).scale(1.0d).addFilter(ExifFilterUtils.getFilterForOrientation(orientation.getThumbnailatorOrientation())).asBufferedImage();
oldBufferedImage.flush();
// Re-parse the metadata after rotation as these are newly generated.
ByteArrayOutputStream tmpOutputStream = new ByteArrayOutputStream(inputByteArray.length);
Thumbnails.of(bufferedImage).scale(1.0d).outputFormat(outputFormat.toString()).toOutputStream(tmpOutputStream);
try {
metadata = getMetadata(tmpOutputStream.toByteArray(), outputFormat);
} catch (IOException | ImageProcessingException e) {
LOGGER.debug("Failed to read rotated image metadata: {}", e.getMessage());
LOGGER.trace("", e);
metadata = new Metadata();
}
if (metadata == null) {
metadata = new Metadata();
}
reencode = true;
}
if (outputProfile == null && dlnaCompliant) {
// Set a suitable output profile.
if (width < 1 || height < 1) {
outputProfile = DLNAImageProfile.getClosestDLNAProfile(bufferedImage.getWidth(), bufferedImage.getHeight(), outputFormat, true);
width = outputProfile.getMaxWidth();
height = outputProfile.getMaxHeight();
} else {
outputProfile = DLNAImageProfile.getClosestDLNAProfile(calculateScaledResolution(bufferedImage.getWidth(), bufferedImage.getHeight(), scaleType, width, height), outputFormat, true);
width = Math.min(width, outputProfile.getMaxWidth());
height = Math.min(height, outputProfile.getMaxHeight());
}
if (DLNAImageProfile.JPEG_RES_H_V.equals(outputProfile)) {
scaleType = ScaleType.EXACT;
}
}
boolean convertColors = bufferedImage.getType() == BufferedImageType.TYPE_CUSTOM.getTypeId() || bufferedImage.getType() == BufferedImageType.TYPE_BYTE_BINARY.getTypeId() || bufferedImage.getColorModel().getColorSpace().getType() != ColorSpaceType.TYPE_RGB.getTypeId();
// Impose DLNA format restrictions
if (!reencode && outputFormat == inputResult.imageFormat && outputProfile != null) {
DLNAComplianceResult complianceResult;
switch(outputFormat) {
case GIF:
case JPEG:
case PNG:
ImageInfo imageInfo;
// metadata is only null at this stage if inputImage != null and no rotation was necessary
if (metadata == null) {
imageInfo = inputImage.getImageInfo();
}
imageInfo = ImageInfo.create(bufferedImage.getWidth(), bufferedImage.getHeight(), inputResult.imageFormat, ImageInfo.SIZE_UNKNOWN, bufferedImage.getColorModel(), metadata, false, true);
complianceResult = DLNAImageProfile.checkCompliance(imageInfo, outputProfile);
break;
default:
throw new IllegalStateException("Unexpected image format: " + outputFormat);
}
reencode = reencode || convertColors || !complianceResult.isFormatCorrect() || !complianceResult.isColorsCorrect();
;
if (!complianceResult.isResolutionCorrect()) {
width = width > 0 && complianceResult.getMaxWidth() > 0 ? Math.min(width, complianceResult.getMaxWidth()) : width > 0 ? width : complianceResult.getMaxWidth();
height = height > 0 && complianceResult.getMaxHeight() > 0 ? Math.min(height, complianceResult.getMaxHeight()) : height > 0 ? height : complianceResult.getMaxHeight();
}
if (trace) {
if (complianceResult.isAllCorrect()) {
LOGGER.trace("Image conversion DLNA compliance check: The source image is compliant");
} else {
LOGGER.trace("Image conversion DLNA compliance check for {}: " + "The source image colors are {}, format is {} and resolution ({} x {}) is {}.\nFailures:\n {}", outputProfile, complianceResult.isColorsCorrect() ? "compliant" : "non-compliant", complianceResult.isFormatCorrect() ? "compliant" : "non-compliant", bufferedImage.getWidth(), bufferedImage.getHeight(), complianceResult.isResolutionCorrect() ? "compliant" : "non-compliant", StringUtils.join(complianceResult.getFailures(), "\n "));
}
}
}
if (convertColors) {
// Preserve alpha channel if the output format supports it
BufferedImageType outputImageType;
if ((outputFormat == ImageFormat.PNG || outputFormat == ImageFormat.PSD) && bufferedImage.getColorModel().getNumComponents() == 4) {
outputImageType = bufferedImage.isAlphaPremultiplied() ? BufferedImageType.TYPE_4BYTE_ABGR_PRE : BufferedImageType.TYPE_4BYTE_ABGR;
} else {
outputImageType = BufferedImageType.TYPE_3BYTE_BGR;
}
BufferedImage convertedImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), outputImageType.getTypeId());
ColorConvertOp colorConvertOp = new ColorConvertOp(null);
colorConvertOp.filter(bufferedImage, convertedImage);
bufferedImage.flush();
bufferedImage = convertedImage;
reencode = true;
}
if (width < 1 || height < 1 || (scaleType == ScaleType.MAX && bufferedImage.getWidth() <= width && bufferedImage.getHeight() <= height) || (scaleType == ScaleType.EXACT && bufferedImage.getWidth() == width && bufferedImage.getHeight() == height)) {
// No resize, just convert
if (!reencode && inputResult.imageFormat == outputFormat) {
// Nothing to do, just return source
// metadata is only null at this stage if inputImage != null
Image result;
if (dlnaThumbnail) {
result = metadata == null ? new DLNAThumbnail(inputImage, outputProfile, false) : new DLNAThumbnail(inputByteArray, outputFormat, bufferedImage, metadata, outputProfile, false);
} else if (dlnaCompliant) {
result = metadata == null ? new DLNAImage(inputImage, outputProfile, false) : new DLNAImage(inputByteArray, outputFormat, bufferedImage, metadata, outputProfile, false);
} else {
result = metadata == null ? new Image(inputImage, false) : new Image(inputByteArray, outputFormat, bufferedImage, metadata, false);
}
bufferedImage.flush();
if (trace) {
LOGGER.trace("No conversion is needed, returning source image with width = {}, height = {} and output {}.", bufferedImage.getWidth(), bufferedImage.getHeight(), dlnaCompliant && outputProfile != null ? "profile: " + outputProfile : "format: " + outputFormat);
}
return result;
} else if (!reencode) {
// Convert format
reencode = true;
}
} else {
boolean force = DLNAImageProfile.JPEG_RES_H_V.equals(outputProfile);
BufferedImage oldBufferedImage = bufferedImage;
if (padToSize && force) {
bufferedImage = Thumbnails.of(bufferedImage).forceSize(width, height).addFilter(new Canvas(width, height, Positions.CENTER, Color.BLACK)).asBufferedImage();
} else if (padToSize) {
bufferedImage = Thumbnails.of(bufferedImage).size(width, height).addFilter(new Canvas(width, height, Positions.CENTER, Color.BLACK)).asBufferedImage();
} else if (force) {
bufferedImage = Thumbnails.of(bufferedImage).forceSize(width, height).asBufferedImage();
} else {
bufferedImage = Thumbnails.of(bufferedImage).size(width, height).asBufferedImage();
}
oldBufferedImage.flush();
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Thumbnails.of(bufferedImage).scale(1.0d).outputFormat(outputFormat.toString()).outputQuality(1.0f).toOutputStream(outputStream);
byte[] outputByteArray = outputStream.toByteArray();
Image result;
if (dlnaThumbnail) {
result = new DLNAThumbnail(outputByteArray, bufferedImage.getWidth(), bufferedImage.getHeight(), outputFormat, null, null, outputProfile, false);
} else if (dlnaCompliant) {
result = new DLNAImage(outputByteArray, bufferedImage.getWidth(), bufferedImage.getHeight(), outputFormat, null, null, outputProfile, false);
} else {
result = new Image(outputByteArray, bufferedImage.getWidth(), bufferedImage.getHeight(), outputFormat, null, null, true, false);
}
if (trace) {
StringBuilder sb = new StringBuilder();
sb.append("Convert colors = ").append(convertColors ? "True" : "False").append(", Re-encode = ").append(reencode ? "True" : "False");
LOGGER.trace("Finished converting {} {} image{}. Output image resolution: {}, {}. Flags: {}", inputResult.width + "×" + inputResult.height, inputResult.imageFormat, orientation != ExifOrientation.TOP_LEFT ? " with orientation " + orientation : "", bufferedImage.getWidth() + "×" + bufferedImage.getHeight(), dlnaCompliant && outputProfile != null ? "profile: " + outputProfile : "format: " + outputFormat, sb);
}
bufferedImage.flush();
return result;
}
Aggregations