/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.tags.base;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter;
import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter;
import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter;
import com.jpexs.decompiler.flash.helpers.ImageHelper;
import com.jpexs.decompiler.flash.tags.TagInfo;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.decompiler.flash.types.BasicType;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.FILLSTYLE;
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.LINESTYLE;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.SerializableImage;
import java.awt.Dimension;
import java.awt.Shape;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Set;
/**
*
* @author JPEXS
*/
public abstract class ImageTag extends DrawableTag {
@SWFType(BasicType.UI16)
public int characterID;
protected SerializableImage cachedImage;
public ImageTag(SWF swf, int id, String name, ByteArrayRange data) {
super(swf, id, name, data);
}
public abstract InputStream getOriginalImageData();
protected abstract SerializableImage getImage();
public abstract Dimension getImageDimension();
public abstract void setImage(byte[] data) throws IOException;
public abstract ImageFormat getImageFormat();
public abstract ImageFormat getOriginalImageFormat();
public boolean importSupported() {
return true;
}
public static ImageFormat getImageFormat(byte[] data) {
return getImageFormat(new ByteArrayRange(data));
}
public static ImageFormat getImageFormat(ByteArrayRange data) {
if (hasErrorHeader(data)) {
return ImageFormat.JPEG;
}
if (data.getLength() > 2 && ((data.get(0) & 0xff) == 0xff) && ((data.get(1) & 0xff) == 0xd8)) {
return ImageFormat.JPEG;
}
if (data.getLength() > 6 && ((data.get(0) & 0xff) == 0x47) && ((data.get(1) & 0xff) == 0x49) && ((data.get(2) & 0xff) == 0x46) && ((data.get(3) & 0xff) == 0x38) && ((data.get(4) & 0xff) == 0x39) && ((data.get(5) & 0xff) == 0x61)) {
return ImageFormat.GIF;
}
if (data.getLength() > 8 && ((data.get(0) & 0xff) == 0x89) && ((data.get(1) & 0xff) == 0x50) && ((data.get(2) & 0xff) == 0x4e) && ((data.get(3) & 0xff) == 0x47) && ((data.get(4) & 0xff) == 0x0d) && ((data.get(5) & 0xff) == 0x0a) && ((data.get(6) & 0xff) == 0x1a) && ((data.get(7) & 0xff) == 0x0a)) {
return ImageFormat.PNG;
}
return ImageFormat.UNKNOWN;
}
public SerializableImage getImageCached() {
if (cachedImage != null) {
return cachedImage;
}
SerializableImage image = getImage();
if (Configuration.cacheImages.get()) {
cachedImage = image;
}
return image;
}
public InputStream getImageData() {
InputStream is = getOriginalImageData();
if (is != null) {
return is;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageHelper.write(getImage().getBufferedImage(), getImageFormat(), baos);
return new ByteArrayInputStream(baos.toByteArray());
}
public static boolean hasErrorHeader(byte[] data) {
return hasErrorHeader(new ByteArrayRange(data));
}
public static boolean hasErrorHeader(ByteArrayRange data) {
if (data.getLength() > 4) {
if ((data.get(0) & 0xff) == 0xff && (data.get(1) & 0xff) == 0xd9
&& (data.get(2) & 0xff) == 0xff && (data.get(3) & 0xff) == 0xd8) {
return true;
}
}
return false;
}
private SHAPEWITHSTYLE getShape() {
RECT rect = getRect();
return getShape(rect, false);
}
public SHAPEWITHSTYLE getShape(RECT rect, boolean fill) {
boolean translated = rect.Xmin != 0 || rect.Ymin != 0;
SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE();
shape.fillStyles = new FILLSTYLEARRAY();
shape.fillStyles.fillStyles = new FILLSTYLE[1];
FILLSTYLE fillStyle = new FILLSTYLE();
fillStyle.fillStyleType = Configuration.shapeImportUseNonSmoothedFill.get()
? FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP : FILLSTYLE.REPEATING_BITMAP;
fillStyle.bitmapId = getCharacterId();
MATRIX matrix = new MATRIX();
matrix.hasScale = true;
if (fill) {
RECT imageRect = getRect();
matrix.scaleX = (int) ((((long) SWF.unitDivisor) << 16) * rect.getWidth() / imageRect.getWidth());
matrix.scaleY = (int) ((((long) SWF.unitDivisor) << 16) * rect.getHeight() / imageRect.getHeight());
} else {
matrix.scaleX = ((int) SWF.unitDivisor) << 16;
matrix.scaleY = matrix.scaleX;
}
if (translated) {
matrix.translateX = rect.Xmin;
matrix.translateY = rect.Ymin;
}
fillStyle.bitmapMatrix = matrix;
shape.fillStyles.fillStyles[0] = fillStyle;
shape.lineStyles = new LINESTYLEARRAY();
shape.lineStyles.lineStyles = new LINESTYLE[0];
shape.shapeRecords = new ArrayList<>();
StyleChangeRecord style = new StyleChangeRecord();
style.stateFillStyle0 = true;
style.fillStyle0 = 1;
style.stateMoveTo = true;
if (translated) {
style.moveDeltaX = rect.Xmin;
style.moveDeltaY = rect.Ymin;
}
shape.shapeRecords.add(style);
StraightEdgeRecord top = new StraightEdgeRecord();
top.generalLineFlag = true;
top.deltaX = rect.getWidth();
StraightEdgeRecord right = new StraightEdgeRecord();
right.generalLineFlag = true;
right.deltaY = rect.getHeight();
StraightEdgeRecord bottom = new StraightEdgeRecord();
bottom.generalLineFlag = true;
bottom.deltaX = -rect.getWidth();
StraightEdgeRecord left = new StraightEdgeRecord();
left.generalLineFlag = true;
left.deltaY = -rect.getHeight();
shape.shapeRecords.add(top);
shape.shapeRecords.add(right);
shape.shapeRecords.add(bottom);
shape.shapeRecords.add(left);
shape.shapeRecords.add(new EndShapeRecord());
return shape;
}
@Override
public RECT getRect() {
return getRect(null); // parameter not used
}
@Override
public RECT getRect(Set<BoundedTag> added) {
Dimension dimension = getImageDimension();
int widthInTwips = (int) (dimension.getWidth() * SWF.unitDivisor);
int heightInTwips = (int) (dimension.getHeight() * SWF.unitDivisor);
return new RECT(0, widthInTwips, 0, heightInTwips);
}
@Override
public int getUsedParameters() {
return 0;
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
return transformation.toTransform().createTransformedShape(getShape().getOutline(swf, stroked));
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
BitmapExporter.export(swf, getShape(), null, image, transformation, strokeTransformation, colorTransform);
}
@Override
public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException {
SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, getShape(), exporter, null, colorTransform, 1);
shapeExporter.export();
}
@Override
public void toHtmlCanvas(StringBuilder result, double unitDivisor) {
CanvasShapeExporter cse = new CanvasShapeExporter(null, unitDivisor, swf, getShape(), null, 0, 0);
cse.export();
result.append(cse.getShapeData());
}
@Override
public int getNumFrames() {
return 1;
}
@Override
public boolean isSingleFrame() {
return true;
}
public void clearCache() {
cachedImage = null;
}
@Override
public void getTagInfo(TagInfo tagInfo) {
super.getTagInfo(tagInfo);
Dimension dimension = getImageDimension();
tagInfo.addInfo("general", "width", dimension.getWidth());
tagInfo.addInfo("general", "height", dimension.getHeight());
}
@Override
public int getCharacterId() {
return characterID;
}
@Override
public void setCharacterId(int characterId) {
this.characterID = characterId;
}
}