/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. * */ package org.apache.flex.compiler.internal.embedding.transcoders; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DirectColorModel; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.MemoryCacheImageOutputStream; import org.apache.commons.io.IOUtils; import org.apache.flex.compiler.common.ISourceLocation; import org.apache.flex.compiler.internal.embedding.EmbedAttribute; import org.apache.flex.compiler.internal.embedding.EmbedData; import org.apache.flex.compiler.internal.workspaces.Workspace; import org.apache.flex.compiler.problems.EmbedExceptionWhileTranscodingProblem; import org.apache.flex.compiler.problems.EmbedQualityRequiresCompressionProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.swf.tags.DefineBitsJPEG3Tag; import org.apache.flex.swf.tags.DefineBitsTag; import org.apache.flex.swf.tags.ICharacterTag; import org.apache.flex.swf.tags.ITag; import org.apache.flex.utils.DAByteArrayOutputStream; /** * Handle the embedding of images which need to be transcoded */ public class JPEGTranscoder extends ImageTranscoder { public JPEGTranscoder(EmbedData data, Workspace workspace) { super(data, workspace); this.compression = false; } private boolean compression; private Float quality; @Override public boolean analyze(ISourceLocation location, Collection<ICompilerProblem> problems) { boolean result = super.analyze(location, problems); baseClassQName = CORE_PACKAGE + ".BitmapAsset"; return result; } @Override protected boolean checkAttributeValues(ISourceLocation location, Collection<ICompilerProblem> problems) { boolean result = super.checkAttributeValues(location, problems); if (!result) return false; // quality doesn't make sense without compression if (!compression && (quality != null)) { problems.add(new EmbedQualityRequiresCompressionProblem(location)); result = false; } return result; } @Override protected boolean setAttribute(EmbedAttribute attribute) { boolean isSupported = true; switch (attribute) { case COMPRESSION: compression = (Boolean)data.getAttribute(EmbedAttribute.COMPRESSION); break; case QUALITY: quality = (Float)data.getAttribute(EmbedAttribute.QUALITY); break; default: isSupported = super.setAttribute(attribute); } return isSupported; } @Override protected Map<String, ICharacterTag> doTranscode(Collection<ITag> tags, Collection<ICompilerProblem> problems) { byte[] bytes = getDataBytes(problems); if (bytes == null) return null; byte[] jpegBytes; byte[] compressedAlphaData; try { ImageInfo imageInfo = getImageInfo(bytes, problems); int imageSize = imageInfo.width * imageInfo.height; byte[] alphaData = new byte[imageSize]; int[] pixels = (int[])imageInfo.pixelGrabber.getPixels(); for (int i = 0; i < imageSize; ++i) { alphaData[i] = (byte)((pixels[i] >> 24) & 0xff); } // need to compress the alpha data compressedAlphaData = deflate(alphaData); jpegBytes = bufferedImageToJPEG(imageInfo, pixels); } catch (Exception e) { problems.add(new EmbedExceptionWhileTranscodingProblem(e)); return null; } DefineBitsTag image = buildImage(jpegBytes, compressedAlphaData); if (image == null) return null; Map<String, ICharacterTag> symbolTags = Collections.singletonMap(data.getQName(), (ICharacterTag)image); return symbolTags; } private byte[] bufferedImageToJPEG(ImageInfo imageInfo, int[] pixels) throws Exception { BufferedImage bufferedImage = new BufferedImage(imageInfo.width, imageInfo.height, BufferedImage.TYPE_INT_ARGB); bufferedImage.setRGB(0, 0, imageInfo.width, imageInfo.height, pixels, 0, imageInfo.width); ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next(); ImageWriteParam writeParam = writer.getDefaultWriteParam(); ColorModel colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff); ImageTypeSpecifier imageTypeSpecifier = new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); writeParam.setDestinationType(imageTypeSpecifier); writeParam.setSourceBands(new int[] {0, 1, 2}); writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); float q = 1.0f; if (quality != null) q = quality.floatValue(); writeParam.setCompressionQuality(q); DAByteArrayOutputStream buffer = new DAByteArrayOutputStream(); writer.setOutput(new MemoryCacheImageOutputStream(buffer)); IIOImage ioImage = new IIOImage(bufferedImage, null, null); writer.write(null, ioImage, writeParam); writer.dispose(); return buffer.getDirectByteArray(); } private DefineBitsTag buildImage(byte[] imageBytes, byte[] alphaBytes) { if (imageBytes == null || alphaBytes == null) return null; DefineBitsJPEG3Tag tag = new DefineBitsJPEG3Tag(); tag.setImageData(imageBytes); tag.setBitmapAlphaData(alphaBytes); tag.setAlphaDataOffset(imageBytes.length); return tag; } public static byte[] deflate(byte[] buf) throws IOException { Deflater deflater = new Deflater(Deflater.BEST_SPEED); DAByteArrayOutputStream out = new DAByteArrayOutputStream(); DeflaterOutputStream deflaterStream = new DeflaterOutputStream(out, deflater); try { deflaterStream.write(buf, 0, buf.length); deflaterStream.finish(); deflater.end(); } finally { IOUtils.closeQuietly(deflaterStream); } return out.getDirectByteArray(); } @Override public boolean equals(Object o) { if (!super.equals(o)) return false; if (!(o instanceof JPEGTranscoder)) return false; JPEGTranscoder t = (JPEGTranscoder)o; if ((compression != t.compression) || !quality.equals(t.quality)) { return false; } return true; } @Override public int hashCode() { int hashCode = super.hashCode(); hashCode += (compression ? 1 : 0); if (quality != null) hashCode ^= quality.hashCode(); return hashCode; } }