com.codename1.tools.resourcebuilder.AnimationImpl Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.tools.resourcebuilder;
import com.codename1.ui.util.EditableResources;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Allows us to define an animated gif as an animation object
*
* @author Shai Almog
*/
public class AnimationImpl {
private File file;
private boolean loop = true;
public void writeResource(DataOutputStream output) throws IOException {
ImageReader iReader = ImageIO.getImageReadersBySuffix("gif").next();
iReader.setInput(ImageIO.createImageInputStream(new FileInputStream(file)));
int frames = iReader.getNumImages(true);
BufferedImage previousImage = null;
int w = 0, h = 0;
List imageList = new ArrayList();
List colorPalette = new ArrayList();
int totalTime = 0;
for(int frameIter = 0 ; frameIter < frames ; frameIter++) {
boolean drawPreviousFrame = false;
BufferedImage currentImage = iReader.read(frameIter);
if(previousImage != null) {
//previousImage = iReader.read(0);
Graphics2D g2d = previousImage.createGraphics();
drawPreviousFrame = !isRestoreToBackgroundColor(iReader, frameIter);
if(!drawPreviousFrame) {
previousImage = new BufferedImage(previousImage.getWidth(), previousImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d.dispose();
g2d = previousImage.createGraphics();
}
Point p = getPixelOffsets(iReader, frameIter);
g2d.drawImage(currentImage, p.x, p.y, null);
g2d.dispose();
} else {
w = currentImage.getWidth();
h = currentImage.getHeight();
previousImage = currentImage;
}
Frame f = new Frame();
f.imageData = new int[w * h];
previousImage.getRGB(0, 0, w, h, f.imageData, 0, w);
// extract the time from gif
f.time = getFrameTime(iReader, frameIter);
f.drawPreviousFrame = drawPreviousFrame;
totalTime += f.time;
// make sure the palette has the colors from all the frames
for(int currentColor : f.imageData) {
if(!colorPalette.contains(currentColor) && colorPalette.size() < 255) {
colorPalette.add(currentColor);
}
}
imageList.add(f);
}
// write the palette for the animation into the resource
output.writeByte(colorPalette.size());
for(int color : colorPalette) {
output.writeInt(color);
}
// write the width/height of the animation
output.writeShort(w);
output.writeShort(h);
// write the number of frames
output.writeByte(imageList.size());
output.writeInt(totalTime);
output.writeBoolean(loop);
for(int iter = 0 ; iter < imageList.size() ; iter++) {
Frame currentFrame = imageList.get(iter);
// the first frame must be a keyframe and doesn't need a timestamp
// and is always a keyframe so its a special case...
if(iter == 0) {
for(int value : currentFrame.imageData) {
output.writeByte(getPaletteIndex(colorPalette, value));
}
} else {
byte[] data = currentFrame.compress(w, imageList.get(iter - 1).imageData, colorPalette);
// the timestamp for the animation frame
output.writeInt(currentFrame.time);
if(data != null) {
// indicate that this is not a keyframe
output.writeBoolean(false);
output.writeBoolean(currentFrame.drawPreviousFrame);
output.write(data);
} else {
// this is a keyframe
output.writeBoolean(true);
for(int value : currentFrame.imageData) {
output.writeByte(getPaletteIndex(colorPalette, value));
}
}
}
}
}
private static int getPaletteIndex(List colorPalette, int value) {
int offset = colorPalette.indexOf(value);
// this can happen if there are more than 256 colors in a gif which is possible with animated gifs
if(offset < 0) {
return 0;
}
return offset;
}
public static Point getPixelOffsets(ImageReader reader, int num) throws IOException {
IIOMetadata meta = reader.getImageMetadata(num);
Point point = new Point(-1,-1);
Node root = meta.getAsTree("javax_imageio_1.0");
//printNodeTree(root);
for (Node c = root.getFirstChild(); c != null; c = c.getNextSibling()) {
String name = c.getNodeName();
if ("Dimension".equals(name)) {
for (c = c.getFirstChild(); c != null; c = c.getNextSibling()) {
name = c.getNodeName();
if ("HorizontalPixelOffset".equals(name))
point.x = getValueAttribute(c);
else if ("VerticalPixelOffset".equals(name))
point.y = getValueAttribute(c);
}
return point;
}
}
return point;
}
public static int getFrameTime(ImageReader reader, int num) throws IOException {
IIOMetadata meta = reader.getImageMetadata(num);
Node parent = meta.getAsTree("javax_imageio_gif_image_1.0");
//printNodeTree(parent);
NodeList root = parent.getChildNodes();
for(int iter = 0 ; iter < root.getLength() ; iter++) {
Node n = root.item(iter);
if(n.getNodeName().equals("GraphicControlExtension")) {
return Integer.parseInt(n.getAttributes().getNamedItem("delayTime").getNodeValue()) * 10;
}
}
return 1000;
}
static boolean isRestoreToBackgroundColor(ImageReader reader, int num) throws IOException {
IIOMetadata meta = reader.getImageMetadata(num);
Node parent = meta.getAsTree("javax_imageio_gif_image_1.0");
//printNodeTree(parent);
NodeList root = parent.getChildNodes();
for(int iter = 0 ; iter < root.getLength() ; iter++) {
Node n = root.item(iter);
if(n.getNodeName().equals("GraphicControlExtension")) {
return n.getAttributes().getNamedItem("disposalMethod").getNodeValue().equalsIgnoreCase("restoreToBackgroundColor");
}
}
return false;
}
private static void printNodeTree(Node n) {
System.out.println(n.getNodeName() + " has attributes: " + getNodeAttrs(n));
NodeList list = n.getChildNodes();
if(list != null && list.getLength() > 0) {
System.out.println("Begin children of : " + n.getNodeName());
for(int iter = 0 ; iter < list.getLength() ; iter++) {
printNodeTree(list.item(iter));
}
System.out.println("End children of : " + n.getNodeName());
}
}
private static String getNodeAttrs(Node n) {
NamedNodeMap map = n.getAttributes();
int size = map.getLength();
String response = "";
for(int iter = 0 ; iter < size ; iter++) {
response += map.item(iter).getNodeName() + " = " + map.item(iter).getNodeValue() + " ";
}
return response;
}
static int getValueAttribute(Node node) {
try {
return Integer.parseInt(node.getAttributes().getNamedItem("value").getNodeValue());
} catch (NumberFormatException e) {
return 0;
}
}
private static class Frame {
public int time;
public int[] imageData;
public boolean drawPreviousFrame;
/**
* Try to compress the frame data, if this fails then return null and a keyframe
* would be inserted. Failure is defined by compression coming out as more than
* the size of the original or close enough.
*/
public byte[] compress(int width, int[] previousFrame, List colorPalette) throws IOException {
ByteArrayOutputStream outputArray = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(outputArray);
// compress the image by writing the row offset (as a short) for a modified
// row followed by its data. If there are too many modified rows compression
// won't be good
int rowCount = previousFrame.length / width;
for(int row = 0 ; row < rowCount ; row++) {
int offset = row * width;
for(int iter = offset ; iter < offset + width ; iter++) {
if(previousFrame[iter] != imageData[iter]) {
// this row is different we need to write it into the stream
output.writeShort(row);
for(int rowIter = 0 ; rowIter < width ; rowIter++) {
output.writeByte(getPaletteIndex(colorPalette, imageData[offset + rowIter]));
}
break;
}
}
}
// indicate EOF...
output.writeShort(-1);
output.close();
byte[] bytes = outputArray.toByteArray();
// if its at least 70% the size then its worth the effort, otherwise
// just store the keyframe
if(bytes.length < imageData.length / 10 * 7) {
return bytes;
}
return null;
}
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public boolean getLoop() {
return loop;
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy