/* ChibiPaint Copyright (c) 2006-2008 Marc Schefer This file is part of ChibiPaint. ChibiPaint is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ChibiPaint 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 for more details. You should have received a copy of the GNU General Public License along with ChibiPaint. If not, see <http://www.gnu.org/licenses/>. */ package com.chibipaint.engine; import java.io.*; import java.util.*; import java.util.zip.*; import android.content.Context; public class CPChibiFile { protected static final byte CHIB[] = { 67, 72, 73, 66 }; protected static final byte IOEK[] = { 73, 79, 69, 75 }; protected static final byte HEAD[] = { 72, 69, 65, 68 }; protected static final byte LAYR[] = { 76, 65, 89, 82 }; protected static final byte ZEND[] = { 90, 69, 78, 68 }; static public boolean write(OutputStream os, CPArtwork a) { try { writeMagic(os); os.flush(); Deflater def = new Deflater(7); DeflaterOutputStream dos = new DeflaterOutputStream(os, def); // OutputStream dos = os; writeHeader(dos, a); for (Object l : a.layers) writeLayer(dos, (CPLayer) l); writeEnd(dos); dos.flush(); dos.close(); return true; } catch (IOException e) { return false; } } static public void writeInt(OutputStream os, int i) throws IOException { byte[] temp = { (byte) (i >>> 24), (byte) (i >>> 16 & 0xff), (byte) (i >>> 8 & 0xff), (byte) (i & 0xff) }; os.write(temp); } static public void writeIntArray(OutputStream os, int arr[]) throws IOException { byte[] temp = new byte[arr.length * 4]; int idx = 0; for (int i : arr) { temp[idx++] = (byte) (i >>> 24); temp[idx++] = (byte) (i >>> 16 & 0xff); temp[idx++] = (byte) (i >>> 8 & 0xff); temp[idx++] = (byte) (i & 0xff); } os.write(temp); } static public void writeMagic(OutputStream os) throws IOException { os.write(CHIB); os.write(IOEK); } static public void writeEnd(OutputStream os) throws IOException { os.write(ZEND); writeInt(os, 0); } static public void writeHeader(OutputStream os, CPArtwork a) throws IOException { os.write(HEAD); // Chunk ID writeInt(os, 16); // ChunkSize writeInt(os, 0); // Current Version: Major: 0 Minor: 0 writeInt(os, a.width); writeInt(os, a.height); writeInt(os, a.getLayersNb()); } static public void writeLayer(OutputStream os, CPLayer l) throws IOException { byte[] title = l.name.getBytes("UTF-8"); os.write(LAYR); // Chunk ID writeInt(os, 20 + l.data.length * 4 + title.length); // ChunkSize writeInt(os, 20 + title.length); // Data offset from start of header writeInt(os, l.blendMode); // layer blend mode writeInt(os, l.alpha); // layer opacity writeInt(os, l.visible ? 1 : 0); // layer visibility and future flags writeInt(os, title.length); os.write(title); writeIntArray(os, l.data); } static public CPArtwork read(Context context, InputStream is) { try { if (!readMagic(is)) return null; // not a ChibiPaint file InflaterInputStream iis = new InflaterInputStream(is); CPChibiChunk chunk = new CPChibiChunk(iis); if (!chunk.is(HEAD)) return null; // not a valid file CPChibiHeader header = new CPChibiHeader(iis, chunk); if (header.version >>> 16 > 0) return null; // the file version is higher than what we can deal with, // bail out CPArtwork a = new CPArtwork(context, header.width, header.height); a.layers.remove(0); // FIXME: it would be better not to have created it in // the first place while (true) { chunk = new CPChibiChunk(iis); if (chunk.is(ZEND)) break; else if (chunk.is(LAYR)) readLayer(iis, chunk, a); else realSkip(iis, chunk.chunkSize); } a.setActiveLayer(0); return a; } catch (IOException e) { return null; } catch (Exception e) { return null; } } static private void readLayer(InputStream is, CPChibiChunk chunk, CPArtwork a) throws IOException { CPLayer l = new CPLayer(a.width, a.height); int offset = readInt(is); l.blendMode = readInt(is); // layer blend mode l.alpha = readInt(is); l.visible = (readInt(is) & 1) != 0; int titleLength = readInt(is); byte[] title = new byte[titleLength]; realRead(is, title, titleLength); l.name = new String(title, "UTF-8"); realSkip(is, offset - 20 - titleLength); readIntArray(is, l.data, l.width * l.height); a.layers.add(l); realSkip(is, chunk.chunkSize - offset - l.width * l.height * 4); } static private void readIntArray(InputStream is, int[] intArray, int size) throws IOException { byte[] buffer = new byte[size * 4]; realRead(is, buffer, size * 4); int off = 0; for (int i = 0; i < size; i++) intArray[i] = (buffer[off++] & 0xff) << 24 | (buffer[off++] & 0xff) << 16 | (buffer[off++] & 0xff) << 8 | buffer[off++] & 0xff; } static public int readInt(InputStream is) throws IOException { return is.read() << 24 | is.read() << 16 | is.read() << 8 | is.read(); } static void realSkip(InputStream is, long bytesToSkip) throws IOException { long skipped = 0, value; while (skipped < bytesToSkip) { value = is.read(); if (value < 0) throw new RuntimeException("EOF!"); skipped++; skipped += is.skip(bytesToSkip - skipped); } } static void realRead(InputStream is, byte[] buffer, int bytesToRead) throws IOException { int read = 0, value; while (read < bytesToRead) { value = is.read(); if (value < 0) throw new RuntimeException("EOF!"); buffer[read++] = (byte) value; read += is.read(buffer, read, bytesToRead - read); } } static public boolean readMagic(InputStream is) throws IOException { byte[] buffer = new byte[4]; realRead(is, buffer, 4); if (!Arrays.equals(buffer, CHIB)) return false; realRead(is, buffer, 4); if (!Arrays.equals(buffer, IOEK)) return false; return true; } static class CPChibiChunk { byte[] chunkType = new byte[4]; int chunkSize; public CPChibiChunk(InputStream is) throws IOException { realRead(is, chunkType, 4); chunkSize = readInt(is); } private boolean is(byte[] chunkType) { return Arrays.equals(this.chunkType, chunkType); } } static class CPChibiHeader { int version, width, height, layersNb; public CPChibiHeader(InputStream is, CPChibiChunk chunk) throws IOException { version = readInt(is); width = readInt(is); height = readInt(is); layersNb = readInt(is); realSkip(is, chunk.chunkSize - 16); } } }