/* * @(#) JpegToSwf.java 1.0 02/10/29 * * Copyright 2002 Orgdot AS. All Rights Reserved. * http://www.orgdot.com/javaopensource * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.swfit.core.image; import java.io.*; /** * A class for converting swf movies to the flash4 format. *
* This piece of code is sprinkled with notes I jotted down as I tried to figure * out the swf file format. As a starting point for learning about flash you are well * advised to start with http://www.openswf.org, and not my personal ramblings. *
* This code is not intended for anything more than displaying jpgs in flash movies, * which, for backwards compability, require wrapping the jpgs in swf headers and footers. *
* Shockwave Flash (c) is the Copyright property of Macromedia. *
* * @author Olaf Havnes * @version 1.0, 10/29/02 * @since SWFIT1.0 */ public final class JpegToSwf extends Object { /** * The following utilities are for manipulating bytes in swf-movies. *
* In swf files, bytes are swapped whenever reading words and dwords: * a 32 bit value B1B2B3B4 is written as B4B3B2B1, and a 16 bit value B1B2 is written as B2B1 */ private final static int swap32bit ( int s )[] { return new int [] {s & 0xff, (s & 0xff00) >> 8, (s & 0xff0000) >> 16, (s & 0xff000000) >> 24}; } private final static int swap16bit ( int s )[] { return new int [] { s & 0xff, (s & 0xff00) >> 8 }; } /** * Set the max number of bits needed in a set of unsigned entries */ private final static int numBitsSigned ( int[] ints ) { int num_bits = 0; for (int i = 0; i < ints.length; i++) while ( (ints [i] >> num_bits) > 0) num_bits ++; return num_bits + 1; // allow for the sign bit } /** * Set the max number of bits needed in one signed entry */ private final static int numBitsSigned ( int i ) { int num_bits = 0; while ( (i >> num_bits) > 0) num_bits ++; return num_bits + 1; // allow for the sign bit } /** * Set the max number of bits needed in one unsigned entry */ private final static int numBitsUnsigned ( int i ) { int num_bits = 0; while ( (i >> num_bits) > 0) num_bits ++; return num_bits; // don't allow for any sign bit } /** * Write a rectangle in twips (where a twip sort of equals 1 pixel x 20) *
* the description of a swf 'rectangle' data type - values are in twips : *
* Field: Type: Comment: *
* Nbits nBits = UB[5] Bits in each rect value field *
* Xmin SB[nBits] X minimum position for rect *
* Xmax SB[nBits] X maximum position for rect *
* Ymin SB[nBits] Y minimum position for rect *
* Ymax SB[nBits] Y maximum position for rect */ private final static int writeTwipRect ( int x, int w, int y, int h )[] { int ints [] = { x * 20, w * 20, y * 20, h * 20 }, // get twips from pixels num_bits = numBitsSigned (ints), tot_bits = (num_bits << 2) + 5, num_bytes = ( (tot_bits & 7) > 0) ? (tot_bits >> 3) + 1 : tot_bits >> 3, bytes[] = new int[num_bytes]; bytes[0] = (num_bits << 3); int which_byte = 0, which_bit = 2; for (int i = 0; i < 4; i++) for (int j = num_bits; --j >= 0;) { int bit = ( ints [i] & (1 << j) ) >> j; if (bit > 0) bytes [which_byte] = bytes [which_byte] + (1 << which_bit); if (--which_bit < 0) { which_byte++; which_bit = 7; } } return bytes; } /** * This routine could have been compressed, but I keep all the flags for clarity * One day I might want to draw another shape... */ private final static int rectangleAtOrigin ( int width, int height )[] { int shape_record_bits[] = new int[6], shape_record[] = new int[6], delta_x = 20 * width, x_bits_needed = numBitsSigned (delta_x), delta_y = 20 * height, y_bits_needed = numBitsSigned (delta_y), x_bits_needed_ = numBitsUnsigned (x_bits_needed - 2), delta_x_complement = ( (1 << x_bits_needed) - delta_x) & ( (1 << x_bits_needed) - 1), y_bits_needed_ = numBitsUnsigned (y_bits_needed - 2), delta_y_complement = ( (1 << y_bits_needed) - delta_y) & ( (1 << y_bits_needed) - 1); shape_record[0] = ( (0 << 6) + // none edge record flag // the five flags (0 << 5) + // new styles flag (0 << 4) + // line style change flag (1 << 3) + // fill style 0 change flag (0 << 2) + // fill style 1 change flag (0 << 1) + // move to flag // NUMBER OF FILL INDEX BITS = 1, means read the next 1 bytes, (1) // fill style 0 = 1 (the first style in the fill array) ); shape_record_bits[0] = 7; shape_record[1] = ( (1 << (x_bits_needed_ + x_bits_needed + 3)) + (1 << (x_bits_needed_ + x_bits_needed + 2)) + ((x_bits_needed - 2) << (x_bits_needed + 2)) + (0 << (x_bits_needed + 1)) + (0 << x_bits_needed) + delta_x ); shape_record_bits[1] = 4 + x_bits_needed + x_bits_needed_; shape_record[2] = ( (1 << (y_bits_needed_ + y_bits_needed + 3)) + (1 << (y_bits_needed_ + y_bits_needed + 2)) + ((y_bits_needed - 2) << (y_bits_needed + 2)) + (0 << (y_bits_needed + 1)) + (1 << y_bits_needed) + delta_y ); shape_record_bits[2] = 4 + y_bits_needed + y_bits_needed_; shape_record[3] = ( (1 << (x_bits_needed_ + x_bits_needed + 3)) + (1 << (x_bits_needed_ + x_bits_needed + 2)) + ((x_bits_needed - 2) << (x_bits_needed + 2)) + (0 << (x_bits_needed + 1)) + (0 << x_bits_needed) + delta_x_complement ); shape_record_bits[3] = 4 + x_bits_needed + x_bits_needed_; shape_record[4] = ( (1 << (y_bits_needed_ + y_bits_needed + 3)) + (1 << (y_bits_needed_ + y_bits_needed + 2)) + ((y_bits_needed - 2) << (y_bits_needed + 2)) + (0 << (y_bits_needed + 1)) + (1 << y_bits_needed) + delta_y_complement ); shape_record_bits[4] = 4 + y_bits_needed + y_bits_needed_; // in the end - pad 6 zeros shape_record[5] = ( (0 << 5) + // non edge shape record (0 << 4) + // the next five flags are 0 - which means end of shape flag (0 << 3) + (0 << 2) + (0 << 1) + (0) ); shape_record_bits[5] = 6; // and then pad to align the bytes - set the rest of the bits to zero int total_shape_bits = shape_record_bits[0] + shape_record_bits[1] + shape_record_bits[2] + shape_record_bits[3] + shape_record_bits[4] + shape_record_bits[5], total_shape_bytes = ((total_shape_bits & 7)>0)?(total_shape_bits >> 3) + 1: total_shape_bits >> 3, bytes[] = new int [total_shape_bytes]; for (int i = 0; i < total_shape_bytes; i++) bytes[i] = 0; bytes [0] = shape_record[0] << 1; int which_byte = 0, which_bit = 0; for (int i = 1; i < 6; i++) for (int j = shape_record_bits[i]; --j >= 0;) { int bit = ( shape_record [i] & (1 << j) ) >> j; if (bit > 0) bytes [which_byte] = bytes [which_byte] + (1 << which_bit); if (--which_bit < 0) { which_byte++; which_bit = 7; } } return bytes; } /** * Call this method with an infile (a jpeg image saved on the disk) and an outfile * (which will be the final swf movie): */ public final static void encode ( String infile, String outfile ) throws IOException { File in_f = new File (infile); byte in_buf[] = null; FileInputStream fis = null; try { fis = new FileInputStream (in_f); in_buf = new byte [(int)in_f.length()]; fis.read (in_buf); } finally { if (fis != null) fis.close(); fis = null; } int data_ints [] = new int [in_buf.length], width_pixels = 0, height_pixels = 0; try { for (int i = 0; ;) data_ints[i] = 0xff & in_buf[i++]; } catch (ArrayIndexOutOfBoundsException aie) {} in_buf = null; // find the jpeg values indicating the width and height header of the file // this is where the photoshop jpg breaks down, since they stick a small preview-jpg // in the header. In effect we would get the width and the height of the thumbnail // for the photoshop jpeg. But Jimi is ok ... for (int i = 0, len = data_ints.length - 4; i <= len; i++) if ( data_ints [i ] == 0xff && data_ints [i+1] == 0xc0 && data_ints [i+2] == 0x00 && data_ints [i+3] == 0x11 ) { width_pixels = (data_ints [i + 7] << 8) + data_ints [i + 8]; height_pixels = (data_ints [i + 5] << 8) + data_ints [i + 6]; break; } if (width_pixels == 0 || height_pixels == 0) { throw new IOException ("Image type not supported"); } // encode byte out_buf[] = encodeData ( data_ints, width_pixels, height_pixels ); // write to disk File out_f = new File (outfile); FileOutputStream fos = new FileOutputStream (out_f); try { fos.write (out_buf); } finally { if (fos != null) fos.close(); fos = null; } } /** * Call this method with an infile (a jpeg image saved on the disk) and an outfile * (which will be the final swf movie): */ private final static byte encodeData ( int data_in[], int width, int height )[] { int swf4[] = {0x46,0x57,0x53,0x04}, // movie_length[], // computed later movie_bounds[] = writeTwipRect (0, width, 0, height), header_remains[] = {0x00,0x0c,0x01,0x00,0x43,0x02,0xff,0xff,0xff}, jpeg2_tag[] = {0x7f,0x05}, jpeg2_data_length[] = swap32bit (6 + data_in.length), jpeg2_remains[] = {0x01,0x00,0xff,0xd9,0xff,0xd8}, jpeg2_in[] = data_in, defineshape_tag[] = {0xbf,0x00}, // defineshape_length[], // computed later defineshape_id[] = {0x02,0x00}, defineshape_bounds[] = writeTwipRect (0, width, 0, height), defineshape_fillstyle_structure[] = {0x01,0x41,0x01,0x00}, defineshape_matrix[] = {0xd9,0x40,0x00,0x05,0x00,0x00,0x00},// it is constant defineshape_linestyle[] = {0x00}, defineshape_fillindex_lineindex_bits[] = {0x10}, shape_records[] = rectangleAtOrigin (width, height), placeobject_tag[] = {0x86,0x06,0x06,0x01,0x00,0x02,0x00,0x00,0x40,0x00,0x00,0x00}, // these 'logically' belong further up - but some of the data is unknown ... defineshape_length[] = swap32bit ( defineshape_id.length + defineshape_bounds.length + defineshape_fillstyle_structure.length + defineshape_matrix.length + defineshape_linestyle.length + defineshape_fillindex_lineindex_bits.length + shape_records.length ), movie_length[] = swap32bit ( swf4.length + 4 + // = movie_length.length movie_bounds.length + header_remains.length + jpeg2_tag.length + jpeg2_data_length.length + jpeg2_remains.length + jpeg2_in.length + defineshape_tag.length + defineshape_length.length + defineshape_id.length + defineshape_bounds.length + defineshape_fillstyle_structure.length + defineshape_matrix.length + defineshape_linestyle.length + defineshape_fillindex_lineindex_bits.length + shape_records.length + placeobject_tag.length ), intstream[] = indexedArray(); // write the swf file intstream = writeToIndexArray (intstream, swf4, 0, swf4.length); intstream = writeToIndexArray (intstream, movie_length, 0, movie_length.length); intstream = writeToIndexArray (intstream, movie_bounds, 0, movie_bounds.length); intstream = writeToIndexArray (intstream, header_remains, 0, header_remains.length); intstream = writeToIndexArray (intstream, jpeg2_tag, 0, jpeg2_tag.length); intstream = writeToIndexArray (intstream, jpeg2_data_length, 0, jpeg2_data_length.length); intstream = writeToIndexArray (intstream, jpeg2_remains, 0, jpeg2_remains.length); intstream = writeToIndexArray (intstream, jpeg2_in, 0, jpeg2_in.length); intstream = writeToIndexArray (intstream, defineshape_tag, 0, defineshape_tag.length); intstream = writeToIndexArray (intstream, defineshape_length, 0, defineshape_length.length); intstream = writeToIndexArray (intstream, defineshape_id, 0, defineshape_id.length); intstream = writeToIndexArray (intstream, defineshape_bounds, 0, defineshape_bounds.length); intstream = writeToIndexArray (intstream, defineshape_fillstyle_structure, 0, defineshape_fillstyle_structure.length); intstream = writeToIndexArray (intstream, defineshape_matrix, 0, defineshape_matrix.length); intstream = writeToIndexArray (intstream, defineshape_linestyle, 0, defineshape_linestyle.length); intstream = writeToIndexArray (intstream, defineshape_fillindex_lineindex_bits, 0, defineshape_fillindex_lineindex_bits.length); intstream = writeToIndexArray (intstream, shape_records, 0, shape_records.length); intstream = writeToIndexArray (intstream, placeobject_tag, 0, placeobject_tag.length); return indexIntsToByteArray (intstream); } /** * The following methods are a set of (propably unhealthy) methods for quickly working with * ints instead of bytes, and they sort of mirror the ByteArrayOutputStream. */ private static final int INT_BUF_SIZE = 8192; public final static int indexedArray ()[] { int arr [] = new int [INT_BUF_SIZE]; arr [0] = 1; return arr; } private final static int copyIndexArray ( int from[], int to[] )[] { System.arraycopy (from, 0, to, 0, from[0]); return to; } public static final int writeToIndexArray ( int arr[], int val )[] { if (arr[0] == arr.length) return writeToIndexArray (copyIndexArray (arr, new int [arr.length * 2]), val); arr [arr[0]++] = val; return arr; } public static final int writeToIndexArray ( int arr [], int vals[], int off, int len )[] { int new_len = arr.length, tot_data_len = len - off + arr[0] + 1; // the first, indexed int while (tot_data_len > new_len) new_len *= 2; if (new_len > arr.length) return writeToIndexArray (copyIndexArray (arr, new int [new_len]), vals, off, len); System.arraycopy (vals, off, arr, arr[0], len); arr[0] += len; return arr; } public static final byte indexIntsToByteArray ( int arr[] )[] { byte buf[] = new byte [ arr[0] - 1 ]; try { for (int i = 0; ; ) buf [i++] = (byte) arr[i]; } catch (ArrayIndexOutOfBoundsException aie) {} return buf; } }