Search in sources :

Example 1 with DLNAImage

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;
}
Also used : ImageProcessingException(com.drew.imaging.ImageProcessingException) BufferedImageType(net.pms.util.BufferedImageType) ImageReaderResult(net.pms.image.ImageIOTools.ImageReaderResult) Canvas(net.coobird.thumbnailator.filters.Canvas) Metadata(com.drew.metadata.Metadata) IIOException(javax.imageio.IIOException) IIOException(javax.imageio.IIOException) BufferedImage(java.awt.image.BufferedImage) DLNAImage(net.pms.dlna.DLNAImage) BufferedImage(java.awt.image.BufferedImage) DLNAThumbnail(net.pms.dlna.DLNAThumbnail) DLNAImage(net.pms.dlna.DLNAImage) ColorConvertOp(java.awt.image.ColorConvertOp) UnknownFormatException(net.pms.util.UnknownFormatException) DLNAComplianceResult(net.pms.dlna.DLNAImageProfile.DLNAComplianceResult)

Aggregations

ImageProcessingException (com.drew.imaging.ImageProcessingException)1 Metadata (com.drew.metadata.Metadata)1 BufferedImage (java.awt.image.BufferedImage)1 ColorConvertOp (java.awt.image.ColorConvertOp)1 IIOException (javax.imageio.IIOException)1 Canvas (net.coobird.thumbnailator.filters.Canvas)1 DLNAImage (net.pms.dlna.DLNAImage)1 DLNAComplianceResult (net.pms.dlna.DLNAImageProfile.DLNAComplianceResult)1 DLNAThumbnail (net.pms.dlna.DLNAThumbnail)1 ImageReaderResult (net.pms.image.ImageIOTools.ImageReaderResult)1 BufferedImageType (net.pms.util.BufferedImageType)1 UnknownFormatException (net.pms.util.UnknownFormatException)1