/*
* @(#)DVBSubpicture.java - decodes DVB subtitles
*
* Copyright (c) 2004-2009 by dvb.matt, All rights reserved
*
* This file is part of ProjectX, a free Java based demux utility.
* By the authors, ProjectX is intended for educational purposes only,
* as a non-commercial test project.
*
*
* 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
*
*/
/*
* example of a basic implementation of a DVB subtitle decoder
*
* it does not yet implement export (only log it) of encoded string characters, only bitmapped pictures
*
*/
/*
* stenographic subtitling patch (BBC) by Duncan (Shannock9) UK
* 2008-12
*/
package net.sourceforge.dvb.projectx.subtitle;
//DM24042004 081.7 int02 introduced
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Arrays;
import net.sourceforge.dvb.projectx.common.Resource;
import net.sourceforge.dvb.projectx.common.Common;
import net.sourceforge.dvb.projectx.common.Keys;
import net.sourceforge.dvb.projectx.subtitle.ColorAreas; //S9
public class DVBSubpicture extends Object {
private byte data[];
private int width = Common.getSettings().getBooleanProperty(Keys.KEY_SubtitlePanel_enableHDSub) ? 1920 : 720;
private int height = Common.getSettings().getBooleanProperty(Keys.KEY_SubtitlePanel_enableHDSub) ? 1088 : 576;
private int BytePosition;
private int BitPosition;
private Graphics2D big;
private BufferedImage bimg;
private Epoch epoch;
private Page page;
private Region region;
private CLUT clut;
private OBJECT object;
private Hashtable epoches = new Hashtable();
private int table_CLUT_8bit[];
private int IRD;
private int epoch_id = 0;
private boolean biglog;
private int[] pixel_data;
private int[] preview_pixel_data;
private long pts;
private int from_index;
private int to_index;
private boolean picture_saved;
private boolean save;
private boolean preview_visible = false;
private int fix_page_id;
//DM13062004 081.7 int04 add
private Hashtable user_table = new Hashtable();
private boolean user_table_enabled;
//DM30072004 081.7 int07 add
private boolean global_error = false;
public DVBSubpicture()
{
table_CLUT_8bit = generateDefaultCLUT_8Bits();
setIRD(8, user_table, false, ""); //DM13062004 081.7 int04 changed
}
public void setIRD(int val, Hashtable table, boolean log, String page_id_str)
{
IRD = val; //2,4,8 = 4,16,256-color support
if (!table.isEmpty())
IRD = Integer.parseInt(table.get("model").toString().trim());
//goes inactive if bit0 is not set through model ID
ColorAreas.initialise(IRD, log, table); //stream startup event //S9 20090113
//sets full 8 bit decode and disable colortable if CA is active
if ((IRD & 1) == 1)
{
IRD = 8;
user_table = null;
user_table_enabled = false;
}
else
{
user_table = table;
user_table_enabled = !user_table.isEmpty();
}
biglog = log;
resetEpoch();
if (page_id_str.trim().length() != 0)
fix_page_id = Integer.parseInt(page_id_str.trim());
else
fix_page_id = -1;
preview_visible = false;
}
private void addBigMessage(String msg)
{
if (!biglog)
return;
System.out.println(msg);
}
private void resetEpoch()
{
epoches.clear();
epoch = setEpoch(epoch_id);
preview_pixel_data = new int[width * height];
picture_saved = false;
save = false;
}
private int getBits(int N)
{
int Pos, Val;
Pos = BitPosition>>>3;
//DM03082004 081.7 int07 add
if (Pos >= data.length - 4)
{
global_error = true;
BitPosition += N;
BytePosition = BitPosition>>>3;
return 0;
}
Val = (0xFF & data[Pos])<<24 |
(0xFF & data[Pos+1])<<16 |
(0xFF & data[Pos+2])<<8 |
(0xFF & data[Pos+3]);
Val <<= BitPosition & 7;
Val >>>= 32-N;
BitPosition += N;
BytePosition = BitPosition>>>3;
return Val;
}
private int nextBits(int N)
{
int Pos, Val;
Pos = BitPosition>>>3;
//DM03082004 081.7 int07 add
if (Pos >= data.length - 4)
{
global_error = true;
return 0;
}
Val = (0xFF & data[Pos])<<24 |
(0xFF & data[Pos+1])<<16 |
(0xFF & data[Pos+2])<<8 |
(0xFF & data[Pos+3]);
Val <<= BitPosition & 7;
Val >>>= 32-N;
return Val;
}
private void flushBits(int N)
{
BitPosition += N;
BytePosition = BitPosition>>>3;
}
private void alignToByte()
{
alignToByte(1);
}
private void alignToByte(int N)
{
while ( (7 & BitPosition) != 0 )
flushBits(N);
}
private void alignToWord()
{
if ((1 & BytePosition) != 0 && nextBits(8) != 0x0F)
flushBits(8);
}
public int getTimeOut()
{
return page.getTimeOut();
}
public int decodeDVBSubpicture(byte packet[], int BPos[], Graphics2D big, BufferedImage bimg, long pts, boolean save, boolean preview_visible)
{
data = packet; //packet + 4 bytes overhead
BytePosition = BPos[0]; //bytpos
BitPosition = BPos[0]<<3; //bitpos
this.big = big;
this.bimg = bimg;
this.pts = pts;
this.save = save;
this.preview_visible = preview_visible;
picture_saved = false;
//DM30072004 081.7 int07 add
global_error = false;
flushBits(8); //padding
int stream_ident = getBits(8); // 0 = std
int segment_type = 0;
int sync_byte = 0;
//DM30072004 081.7 int07 changed
//while ( nextBits(8) == 0x0F )
for (int ret; BytePosition < data.length - 4; )
{
ret = nextBits(8);
addBigMessage("ret " + Integer.toHexString(ret) + " /bi " + BitPosition + " /by " + BytePosition);
if (ret == 0xFF)
break;
if (ret != 0x0F)
{
flushBits(8);
continue;
}
segment_type = Subtitle_Segment();
alignToByte();
if (global_error && region != null)
region.setError(4);
}
if ( nextBits(8) == 0xFF )
{}
if (picture_saved)
return -1;
return -2;
}
private int Subtitle_Segment()
{
if ( getBits(8) != 0x0F ) //syncbyte
return -1;
int segment_type = getBits(8);
int page_id = getBits(16); //page_id
if (fix_page_id >= 0 && page_id != fix_page_id) // exclude unwanted pages
{
stuffing();
return 0xFF;
}
page = epoch.setPage(page_id);
addBigMessage("segm: 0x" + Integer.toHexString(segment_type) + " / " + pts);
switch (segment_type)
{
case 0x10:
return page_composition();
//return segment_type;
case 0x11:
region_composition();
return segment_type;
case 0x12:
CLUT_definition();
return segment_type;
case 0x13:
object_data();
return segment_type;
case 0x80:
end_display();
return segment_type;
case 0xFF:
stuffing();
return segment_type;
default:
stuffing();
return segment_type;
}
}
private void end_display()
{
int segment_length = getBits(16);
flushBits(segment_length * 8); //DM18062004 081.7 int05 changed
}
private void stuffing()
{
int segment_length = getBits(16); //+ BytePosition;
flushBits(segment_length * 8); //DM18062004 081.7 int05 changed
}
private void prepare_output()
{
//DM26052004 081.7 int03 changed
//long new_time_out = (long)Math.round((pts - page.getTimeIn()) / 900.0); 1000
long new_time_out = 1L + ((pts - page.getTimeIn()) / 1024);
if (page.getTimeOut() > 0 && new_time_out > page.getTimeOut()) // maybe wrong
new_time_out = page.getTimeOut();
// if (page.getTimeOut() > 0 && new_time_out > (page.getTimeOut() / 10))
// new_time_out = page.getTimeOut() / 10;
if (page.getTimeOut() > 0 && pts == -1) // -1 means take proposed play time
new_time_out = page.getTimeOut() / 10;
//
if (page.getTimeOut() > 0 && Common.getCollection().getSettings().getBooleanProperty(Keys.KEY_SubtitlePanel_rebuildPictPTS)) // -1 means take proposed play time
new_time_out = page.getTimeOut() / 10;
//
//Common.setMessage("!> debug Info : px " + page.getX() + " / py " + page.getY() + " / pw " + page.getWidth() + " / ph " + page.getHeight() + " (VN): " + page.getVersionNumber());
if (page.getWidth() <= 0 || page.getHeight() <= 0 || page.getWidth() > width || page.getHeight() > height)
{
Common.setMessage("!> Page ignored (VN): " + page.getVersionNumber() + "; (size error) " + page.getWidth() + " * " + page.getHeight());
return;
}
int page_pixel_data[] = new int[page.getWidth() * page.getHeight()];
for (int y = 0; y < page.getHeight(); y++)
System.arraycopy(preview_pixel_data, page.getX() + ((page.getY() + y) * width), page_pixel_data, y * page.getWidth(), page.getWidth());
if (page.getWriteStatus())
BMP.savePixels( new Bitmap( page.getX(), page.getY(), page.getWidth(), page.getHeight(), page_pixel_data, IRD, page.getId(), region.getId(), 0, page.getTimeIn(), (int)new_time_out));
if (preview_visible)
bimg.setRGB(page.getX(), page.getY(), page.getWidth(), page.getHeight(), page_pixel_data, 0, page.getWidth());
paintRegionBorder(page.getX(), page.getY(), page.getWidth(), page.getHeight());
picture_saved = true;
addBigMessage("time: in " + page.getTimeIn() + " /len " + new_time_out + " /save " + save + " /prev " + preview_visible //S9dbg
+ " <<prepare.output>>"); //S9dbg
}
private int page_composition()
{
int segment_end = getBits(16) + BytePosition;
int segment_type = 0x10;
int time_out = getBits(8) * 100; //page_time_out, milliseconds (here ticks) 'til disappearing without another erase event
page.setVersionNumber(getBits(4)); //page_version_number, if number exists, update isn't necessary
page.setState(getBits(2)); //page_state
//0 = only updates of this page contents
//1 = new page instance, all contents with new definitions
//2 = new epoch, see 1, reset all, complete dec. model (IRD) changes possible
flushBits(2);
addBigMessage("pagecomp: state " + page.getState() + " /page " + page.getId() + " /pv " + page.getVersionNumber() + " /to " + time_out //S9dbg
+ " " + ((page.getState()<1) ? "" : (page.getState()<2) ? "<<acquisition pt>>" : "<<new epoch>>")); //S9dbg
if (page.getState() > 0)
{
clearBackground();
page.clearArea();
}
for (Enumeration e = epoch.getRegions(); e.hasMoreElements() ; )
{
region = epoch.setRegion(Integer.parseInt(e.nextElement().toString()));
//DM23062004 081.7 int05 add
if (region.getErrors() > 0)
{
Common.setMessage(Resource.getString("subpicture.msg.error.dvbdecoding", "" + region.getErrors(), "" + region.getId(), "" + page.getTimeIn()));
if ((region.getErrors() & 4) != 0)
{
Common.setMessage("!> Region ignored (VN): " + region.getVersionNumber());
region.setActive(false);
}
}
addBigMessage("enum: region " + region.getId() + " /err " + region.getErrors() + " /acti " + region.isActive() + " /chng " + region.isChanged() + " /ti_o " + time_out);
if ( page.getState() == 0 && time_out > 6000 )
continue;
region.setError(0); //DM23062004 081.7 int05 add
if ( !region.isActive() || !region.isChanged() )
continue;
region.setChanged(false);
pixel_data = region.getPixel();
page.addArea(region.getXBound(), region.getYBound(), region.getWidth(), region.getHeight());
try {
for (int a = 0; a < region.getHeight(); a++)
System.arraycopy(pixel_data, a * region.getWidth(), preview_pixel_data, region.getXBound() + ((region.getYBound() + a) * width), region.getWidth());
} catch (Exception ex) {
region.setError(8);
Common.setMessage(Resource.getString("subpicture.msg.error.dvbdecoding", "" + region.getErrors(), "" + region.getId(), "" + page.getTimeIn()));
}
segment_type = 0x80;
addBigMessage("addToBMP: region " + region.getId());
addBigMessage("newSize: x " + page.getX() + " y " + page.getY() + " w " + page.getWidth() + " h " + page.getHeight());
}
if (segment_type == 0x80)
prepare_output();
if (page.getState() > 0) //if not update, page content has to die
{
page = epoch.newPage(page.getId());
page.setTimeIn(pts);
page.setWriteStatus(save);
epoch.clearRegions();
epoch.clearObjects();
}
page.setTimeIn(pts); // for stenographic subtitling //S9
page.setTimeOut(time_out); //page_time_out, seconds to stand
//empty pages may define CLUTs without regions
while (BytePosition < segment_end)
{
region = epoch.setRegion(getBits(8)); //region_ids of this epoch
region.setActive(true);
region.setChanged(true);
flushBits(8);
region.setHorizontalAddress(getBits(16)); // start_x
region.setVerticalAddress(getBits(16)); // start_y
addBigMessage("addreg: reg " + region.getId() + " /x " + region.getXBound() + " /y " + region.getYBound());
}
return segment_type;
}
private void region_composition()
{
int segment_end = getBits(16) + BytePosition;
region = epoch.setRegion(getBits(8)); //region_id
region.setVersionNumber(getBits(4)); //region_version_number, if number exists, update isn't necessary
region.setFillFlag(getBits(1));
flushBits(3);
int old_w = region.getWidth();
int old_h = region.getHeight();
region.setWidth(getBits(16));
region.setHeight(getBits(16));
region.setLevelOfCompatibility(getBits(3));
region.setDepth(getBits(3));
flushBits(2);
int CLUT_id = getBits(8);
clut = epoch.setCLUT(CLUT_id); //CLUT_id
region.setCLUT_id(CLUT_id);
//background pixel code
region.setPixelCode_8bit(getBits(8));
region.setPixelCode_4bit(getBits(4));
region.setPixelCode_2bit(getBits(2));
if (!region.isActive() || !region.getFillFlag()) //retain prev obj data //S9
{
if (old_w == region.getWidth() && old_h == region.getHeight())
pixel_data = region.getPixel(); //...for stenographic //S9
else //S9
pixel_data = region.initPixel();
}
else //S9
pixel_data = region.initPixel();
if (pixel_data == null) //but during acquisition //S9
pixel_data = region.initPixel(); //...need dummy previous //S9
flushBits(2);
paintRegionBackground();
addBigMessage("regcomp: page " + page.getId() + " /reg " + region.getId() + " /rv " + region.getVersionNumber()
+ " /rw " + region.getWidth() + " /rh " + region.getHeight() + " /pxl " + pixel_data.length
+ " /lv " + region.getCompatibility() + " /clut " + clut.getId()
+ " /activ " + region.isActive() + " /fill " + region.getFillFlag()); //S9
while (BytePosition < segment_end)
{
object = epoch.setObject(getBits(16)); //object_id
object.setRegionId(region.getId());
object.setType(getBits(2));
object.setProvider(getBits(2));
object.setHorizontalPosition(getBits(12));
flushBits(4);
object.setVerticalPosition(getBits(12));
if (object.getType() == 1 || object.getType() == 2) //character or ch.strings
{
object.setForegroundCode(getBits(8)); //foreground_pixel_code
object.setBackgroundCode(getBits(8)); //background_pixel_code
}
addBigMessage("addobj: reg " + region.getId() + " /obj " + Integer.toHexString(object.getId()).toUpperCase() + " /x " + object.getHorizontalPosition() + " /y " + object.getVerticalPosition());
}
}
private void CLUT_definition()
{
int segment_end = getBits(16) + BytePosition;
clut = epoch.setCLUT(getBits(8)); //CLUT_id
clut.setVersionNumber(getBits(4)); //CLUT_version_number, if number exists, update isn't necessary
addBigMessage("clutcomp: " + clut.getId() + " /v " + clut.getVersionNumber());
flushBits(4);
//user table
//DM13062004 081.7 int04 add
if (user_table_enabled)
{
setUserClut();
flushBits( (segment_end - BytePosition) * 8);
return;
}
while (BytePosition < segment_end)
{
int CLUT_entry_id = getBits(8);
int CLUT_flag_2bit_entry = getBits(1);
int CLUT_flag_4bit_entry = getBits(1);
int CLUT_flag_8bit_entry = getBits(1);
int flag = CLUT_flag_8bit_entry<<3
| CLUT_flag_4bit_entry<<2
| CLUT_flag_2bit_entry<<1;
flushBits(4);
int full_range_flag = getBits(1);
int ARGB, Y, Cr, Cb, T;
if (full_range_flag == 1)
{
Y = getBits(8);
Cr = getBits(8);
Cb = getBits(8);
T = getBits(8);
}
else //only MSB transmitted
{
Y = getBits(6)<<2;
Cr = getBits(4)<<4;
Cb = getBits(4)<<4;
T = getBits(2)<<6;
}
ARGB = YUVtoRGB(Y, Cr, Cb, T);
addBigMessage("addclut: " + CLUT_entry_id + " /flag " + Integer.toHexString(flag).toUpperCase() + " /ARGB " + Integer.toHexString(ARGB).toUpperCase() + " /range " + full_range_flag);
for (int i=0; i<3; i++)
clut.setClutEntry(mapColorIndex(CLUT_entry_id, getRegionDepth(), 2<<i), (flag & 2<<i), ARGB);
}
}
private void object_data()
{
int segment_end = getBits(16) + BytePosition;
object = epoch.setObject(getBits(16)); //object_id
int region_id = object.getRegionId();
if (region_id < 0)
{
flushBits( (segment_end - BytePosition) * 8); //DM18062004 081.7 int05 changed
addBigMessage("object_id " + object.getId() + " with no region_id");
return;
}
region = epoch.setRegion(region_id); //use the right region
region.resetXY();
pixel_data = region.getPixel();
addBigMessage("objdata: reg " + region.getId() + " /obj " + Integer.toHexString(object.getId()).toUpperCase());
object.setVersionNumber(getBits(4)); //object_version_number, if number exists, update isn't necessary
int object_coding_method = getBits(2);
object.setNonModify(getBits(1)); //non_modifying_colour_flag
flushBits(1);
//assign region specific CLUT
clut = epoch.setCLUT(region.getCLUT_id());
/**
* modify user_table here, if no clut definition were sent
*/
if (user_table_enabled && clut.getModifyFlags() == 0)
setUserClut();
if (object_coding_method == 0) //pixels
{
int top_field_data_block_end = getBits(16);
int bottom_field_data_block_end = getBits(16);
object.setCopyTopFieldFlag(bottom_field_data_block_end);
top_field_data_block_end += BytePosition;
while (BytePosition < top_field_data_block_end)
pixel_block();
region.nextField();
bottom_field_data_block_end += BytePosition;
while (BytePosition < bottom_field_data_block_end) //if 0-length, copy top field line
pixel_block();
//alignToWord(); // flush 8 bits if not aligned
}
if (object_coding_method == 1) //text chars
{
int number_of_codes = getBits(8); //all chars in a line
int character_code;
String str = "";
for (int i=0; i < number_of_codes; i++)
str += (char)(character_code = getBits(16));
addBigMessage("chars: " + str);
paintStringObjects(region.getX(0), region.getY(), str);
}
}
private void pixel_block()
{
int data_type = getBits(8);
if (data_type == 0x10)
{
while (pixel_code_string_2bit() > 0)
{}
alignToByte(); // flush 2 bits if not aligned
}
else if (data_type == 0x11)
{
while (pixel_code_string_4bit() > 0)
{}
alignToByte(); //flush 4 bits if not aligned
}
else if (data_type == 0x12)
{
while (pixel_code_string_8bit() > 0)
{}
}
else if (data_type == 0x20)
for (int a=0; a<4; a++) //getBits(16) = 4 entries
object.setMapTable_2to4bit( a, getBits(4));
else if (data_type == 0x21)
for (int a=0; a<4; a++) //getBits(32) = 4 entries
object.setMapTable_2to8bit( a, getBits(8));
else if (data_type == 0x22)
for (int a=0; a<16; a++) //getBits(128) = 16 entries
object.setMapTable_4to8bit( a, getBits(8));
else if (data_type == 0xF0)
{
if (object.getCopyTopFieldFlag())
paintCopiedField(region.getX(0), region.getY() + 1, region.getWidth());
region.nextLine();
}
}
private int pixel_code_string_2bit()
{
if (nextBits(2) != 0)
paintPixelLine(region.getX(1), region.getY(), 1, getBits(2), 2);
else
{
flushBits(2);
if (getBits(1) == 1) //switch_1
{
int run_length_3to10 = getBits(3) + 3; //pixel_len + 3
paintPixelLine(region.getX(run_length_3to10), region.getY(), run_length_3to10, getBits(2), 2);
}
else
{
int switch_2 = getBits(1);
if (switch_2 == 1)
paintPixelLine(region.getX(1), region.getY(), 1, 0, 2);
else if (switch_2 == 0)
{
int switch_3 = getBits(2);
if (switch_3 == 0)
return 0; // end_of_string_signal = 0
else if (switch_3 == 1)
paintPixelLine(region.getX(2), region.getY(), 2, 0, 2);
else if (switch_3 == 2)
{
int run_length_12to27 = getBits(4) + 12; //pixel_len + 29
paintPixelLine(region.getX(run_length_12to27), region.getY(), run_length_12to27, getBits(2), 2);
}
else if (switch_3 == 3)
{
int run_length_29to284 = getBits(8) + 29; //pixel_len + 29
paintPixelLine(region.getX(run_length_29to284), region.getY(), run_length_29to284, getBits(2), 2);
}
}
}
}
return 1;
}
private int pixel_code_string_4bit()
{
if (nextBits(4) != 0) //1 pixel of color enry 1..15
paintPixelLine(region.getX(1), region.getY(), 1, getBits(4), 4);
else
{
flushBits(4);
if (getBits(1) == 0) //switch_1
{
if (nextBits(3) != 0)
{
int run_length_3to9 = getBits(3) + 2; //pixel_len + 2
paintPixelLine(region.getX(run_length_3to9), region.getY(), run_length_3to9, 0, 4);
}
else
return getBits(3); // end_of_string_signal = 0
}
else
{
if (getBits(1) == 0) //switch_2
{
int run_length_4to7 = getBits(2) + 4; //pixel_len + 4
paintPixelLine(region.getX(run_length_4to7), region.getY(), run_length_4to7, getBits(4), 4);
}
else
{
int switch_3 = getBits(2);
if (switch_3 < 2)
{
int run_length_1to2 = switch_3 + 1; //pixel_len (1 or 2)
paintPixelLine(region.getX(run_length_1to2), region.getY(), run_length_1to2, 0, 4);
}
else if (switch_3 == 2)
{
int run_length_9to24 = getBits(4) + 9; //pixel_len + 9
paintPixelLine(region.getX(run_length_9to24), region.getY(), run_length_9to24, getBits(4), 4);
}
else if (switch_3 == 3)
{
int run_length_25to280 = getBits(8) + 25; //pixel_len + 25
paintPixelLine(region.getX(run_length_25to280), region.getY(), run_length_25to280, getBits(4), 4);
}
}
}
}
return 1;
}
private int pixel_code_string_8bit()
{
int pixel_code_8bit;
if (nextBits(8) != 0)
paintPixelLine(region.getX(1), region.getY(), 1, getBits(8), 8);
else
{
flushBits(8);
if (getBits(1) == 0) //switch_1
{
if (nextBits(7) != 0)
{
int run_length_1to127 = getBits(7); //pixel_len
paintPixelLine(region.getX(run_length_1to127), region.getY(), run_length_1to127, 0, 8);
}
else
return getBits(7); //end_of_string_signal = 0
}
else
{
int run_length_3to127 = getBits(7); //pixel_len with colorindex in next 8bits
paintPixelLine(region.getX(run_length_3to127), region.getY(), run_length_3to127, getBits(8), 8);
}
}
return 1;
}
private int YUVtoRGB(int Y, int Cr, int Cb, int T)
{
if (Y == 0)
return 0;
int R = (int)((float)Y +1.402f * (Cr-128));
int G = (int)((float)Y -0.34414 * (Cb-128) -0.71414 * (Cr-128));
int B = (int)((float)Y +1.722 * (Cb-128));
R = R < 0 ? 0 : (R > 0xFF ? 0xFF : R);
G = G < 0 ? 0 : (G > 0xFF ? 0xFF : G);
B = B < 0 ? 0 : (B > 0xFF ? 0xFF : B);
T = 0xFF - (T < 0 ? 0 : (T > 0xFF ? 0xFF : T));
return (T<<24 | R<<16 | G<<8 | B);
}
private int[] generateDefaultCLUT_8Bits()
{
int table[] = new int[256];
for (int i=0; i<256; i++)
table[i] = generateClutEntry_8Bits(i);
return table;
}
private int generateClutEntry_4Bits(int i)
{
int T, R, G, B;
if ((i & 8) == 0)
{
if ((i & 7) == 0)
T = R = G = B = 0;
else
{
R = (i & 1) != 0 ? 0xFF : 0;
G = (i & 2) != 0 ? 0xFF : 0;
B = (i & 4) != 0 ? 0xFF : 0;
T = 0xFF;
}
}
else
{
R = (i & 1) != 0 ? 0x80 : 0;
G = (i & 2) != 0 ? 0x80 : 0;
B = (i & 4) != 0 ? 0x80 : 0;
T = 0xFF;
}
return (T<<24 | R<<16 | G<<8 | B);
}
private int generateClutEntry_8Bits(int i)
{
int T=0, R=0, G=0, B=0;
if ((i & 0x88) == 0)
{
if ((i & 0x70) == 0)
{
if ((i & 7) == 0)
T = R = G = B = 0;
else
{
R = (i & 1) != 0 ? 0xFF : 0;
G = (i & 2) != 0 ? 0xFF : 0;
B = (i & 4) != 0 ? 0xFF : 0;
T = 0x40;
}
}
else
{
R = ((i & 1) != 0 ? 0x55 : 0) + ((i & 0x10) != 0 ? 0xAA : 0);
G = ((i & 2) != 0 ? 0x55 : 0) + ((i & 0x20) != 0 ? 0xAA : 0);
B = ((i & 4) != 0 ? 0x55 : 0) + ((i & 0x40) != 0 ? 0xAA : 0);
T = 0xFF;
}
}
else if ((i & 0x88) == 8)
{
R = ((i & 1) != 0 ? 0x55 : 0) + ((i & 0x10) != 0 ? 0xAA : 0);
G = ((i & 2) != 0 ? 0x55 : 0) + ((i & 0x20) != 0 ? 0xAA : 0);
B = ((i & 4) != 0 ? 0x55 : 0) + ((i & 0x40) != 0 ? 0xAA : 0);
T = 0x80;
}
else if ((i & 0x88) == 0x80)
{
R = ((i & 1) != 0 ? 0x2A : 0) + ((i & 0x10) != 0 ? 0x55 : 0) + 0x80;
G = ((i & 2) != 0 ? 0x2A : 0) + ((i & 0x20) != 0 ? 0x55 : 0) + 0x80;
B = ((i & 4) != 0 ? 0x2A : 0) + ((i & 0x40) != 0 ? 0x55 : 0) + 0x80;
T = 0xFF;
}
else if ((i & 0x88) == 0x88)
{
R = ((i & 1) != 0 ? 0x2A : 0) + ((i & 0x10) != 0 ? 0x55 : 0);
G = ((i & 2) != 0 ? 0x2A : 0) + ((i & 0x20) != 0 ? 0x55 : 0);
B = ((i & 4) != 0 ? 0x2A : 0) + ((i & 0x40) != 0 ? 0x55 : 0);
T = 0xFF;
}
return (T<<24 | R<<16 | G<<8 | B);
}
private void paintRegionBackground()
{
if (!region.isActive() || !region.getFillFlag())
return;
int color;
if (IRD == 2)
color = clut.getCLUT_2bit()[mapColorIndex(region.getPixelCode_2bit(), 2, IRD)];
else if (IRD == 4)
color = clut.getCLUT_4bit()[mapColorIndex(region.getPixelCode_4bit(), 4, IRD)];
else
color = clut.getCLUT_8bit()[mapColorIndex(region.getPixelCode_8bit(), 8, IRD)];
color = scaleRGB(color);
Arrays.fill(pixel_data, color);
}
//
private void paintRegionBorder(int x, int y, int w, int h)
{
big.setColor(Color.white);
big.drawRect( x -1, y -1, w +2, h +1);
big.drawString("x" + x + ", y" + y + " / " + w + "*" + h, x , y - 6);
}
// only for painted preview picture, else not necessary
private void clearBackground()
{
Arrays.fill(preview_pixel_data, 0x60);
big.setColor( new Color(0, 0, 0x60)); //deep blue to see full transparency
big.fillRect( bimg.getMinX(), bimg.getMinY(), bimg.getWidth(), bimg.getHeight());
}
private void paintPixelLine(int x, int y, int w, int color_index, int depth)
{
x += object.getHorizontalPosition();
y += object.getVerticalPosition();
int color = 0, color_model = IRD;
color_index = mapColorIndex(color_index, depth, IRD);
if (clut.getModifyFlags() > 0 && (clut.getModifyFlags() & IRD) == 0) //remap if no alternative color def.
{
color_index = mapColorIndex(color_index, IRD, depth);
color_model = depth;
}
if (color_model == 2)
color = clut.getCLUT_2bit()[color_index];
else if (color_model == 4)
color = clut.getCLUT_4bit()[color_index];
else
color = clut.getCLUT_8bit()[color_index];
color = scaleRGB(color);
if ((0xFF000000 & color) == 0) //keep underlying pixel if new pixel is full transparent
return;
//DM23062004 081.7 int05 add
if (x > region.getWidth() - 1 || y > region.getHeight() - 1)
{
region.setError(2);
return;
}
from_index = x + y * region.getWidth();
to_index = from_index + w;
//DM23062004 081.7 int05 add
if (x + w > region.getWidth())
{
to_index = from_index + region.getWidth() - x;
region.setError(1);
}
//don't fit into the pixel area
if (from_index < 0 || from_index > pixel_data.length || to_index < 0 || to_index > pixel_data.length)
{
region.setError(1);
return;
}
Arrays.fill(pixel_data, from_index, to_index, color);
}
// not yet exported, only info for existence
private void paintStringObjects(int x, int y, String str)
{
x += object.getHorizontalPosition();
y += object.getVerticalPosition();
big.setColor(Color.cyan);
big.drawString(str, x , y + 26);
}
private void paintCopiedField(int x, int y, int w)
{
System.arraycopy(pixel_data, x + y * w, pixel_data, x + (y + 1) * w, w);
}
private int scaleRGB(int ARGB)
{
if ((ARGB & 0xFF000000) == 0) //deep blue to see full transparency
return 0x60;
int R = 15 + (0xFF & ARGB>>>16);
int G = 15 + (0xFF & ARGB>>>8);
int B = 15 + (0xFF & ARGB);
R = R > 0xEB ? 0xEB : R;
G = G > 0xEB ? 0xEB : G;
B = B > 0xEB ? 0xEB : B;
// color float scale 16..235 for RGB
//int R = 16 + (int)(0.85546875f * (0xFF & ARGB>>>16));
//int G = 16 + (int)(0.85546875f * (0xFF & ARGB>>>8));
//int B = 16 + (int)(0.85546875f * (0xFF & ARGB));
int T = 0xFF & ARGB>>>24;
return (T<<24 | R<<16 | G<<8 | B);
}
private int mapColorIndex(int color_index, int depth, int new_depth) // depth is 2,4,8!
{
switch (new_depth)
{
case 2:
if (depth == 8)
color_index >>>= 4;
if (depth > 2)
color_index = (2 & color_index>>>2) | (1 & color_index>>>2) | (1 & color_index>>>1) | (1 & color_index);
break;
case 4:
if (depth == 2)
color_index = (object != null) ? object.getMapTable_2to4bit()[color_index] : color_index;
else if (depth == 8)
color_index >>>= 4;
break;
case 8:
if (depth == 2)
color_index = (object != null) ? object.getMapTable_2to8bit()[color_index] : color_index;
else if (depth == 4)
color_index = (object != null) ? object.getMapTable_4to8bit()[color_index] : color_index;
}
return color_index;
}
//in case of undefined regions take regular 8 bit (256col) depth = flag 4
private int getRegionDepth()
{
return (region != null ? region.getDepth() : 4);
}
//DM13062004 081.7 int04 add
private void setUserClut()
{
int model = Integer.parseInt(user_table.get("model").toString().trim());
int max_indices = model > 2 ? (model > 4 ? 256 : 16) : 4;
if (getRegionDepth() < model)
max_indices = getRegionDepth();
for (int i = 0; i < max_indices; i++)
{
if (user_table.containsKey("" + i))
{
addBigMessage("addUserClut: " + i + " /ARGB " + user_table.get("" + i));
clut.setClutEntry(mapColorIndex(i, getRegionDepth(), model), model, (int)Long.parseLong(user_table.get("" + i).toString().trim(), 16));
}
}
}
private Epoch setEpoch(int epoch_id)
{
String epoch_id_str = "" + epoch_id;
if ( !epoches.containsKey(epoch_id_str) )
epoches.put(epoch_id_str, new Epoch(epoch_id) );
return (Epoch)epoches.get(epoch_id_str);
}
class Epoch
{
private int id;
private Hashtable pages = new Hashtable();
private Hashtable cluts = new Hashtable();
private Hashtable regions = new Hashtable();
private Hashtable objects = new Hashtable();
private Epoch()
{}
private Epoch(int val)
{
id = val;
}
private void setId(int val)
{
id = val;
}
private int getId()
{
return id;
}
private Page setPage(int page_id)
{
String page_id_str = "" + page_id;
if ( !pages.containsKey(page_id_str) )
pages.put(page_id_str, new Page(page_id) );
return (Page)pages.get(page_id_str);
}
private Page newPage(int page_id)
{
String page_id_str = "" + page_id;
pages.put(page_id_str, new Page(page_id) );
return (Page)pages.get(page_id_str);
}
private Region setRegion(int region_id)
{
String region_id_str = "" + region_id;
if (!regions.containsKey(region_id_str) )
regions.put(region_id_str, new Region(region_id) );
return (Region)regions.get(region_id_str);
}
private CLUT setCLUT(int CLUT_id)
{
String CLUT_id_str = "" + CLUT_id;
if ( !cluts.containsKey(CLUT_id_str) )
cluts.put(CLUT_id_str, new CLUT(CLUT_id) );
return (CLUT)cluts.get(CLUT_id_str);
}
private OBJECT setObject(int object_id)
{
String object_id_str = "" + object_id;
if ( !objects.containsKey(object_id_str) )
objects.put(object_id_str, new OBJECT(object_id) );
return (OBJECT)objects.get(object_id_str);
}
private void clearRegions()
{
for (Enumeration e = regions.keys(); e.hasMoreElements() ; )
((Region)regions.get(e.nextElement().toString())).setActive(false);
}
private Enumeration getRegions()
{
return regions.keys();
}
private void clearObjects()
{
objects.clear();
}
}
class Page
{
private int id;
private int version_number = -1;
private long time_in = 0;
private int time_out = 0;
private int state;
private int minX = width;
private int minY = height;
private int maxX = 0;
private int maxY = 0;
private int pixel[];
private boolean write = false;
private Page()
{}
private Page(int val)
{
id = val;
}
private void setId(int val)
{
id = val;
}
private int getId()
{
return id;
}
private void setVersionNumber(int val) //modulo16, if a change
{
version_number = val;
}
private int getVersionNumber()
{
return version_number;
}
private void setWriteStatus(boolean b)
{
write = b;
}
private boolean getWriteStatus()
{
return write;
}
private void setTimeIn(long val)
{
time_in = val;
}
private long getTimeIn()
{
return time_in;
}
private void setTimeOut(int val)
{
time_out = val;
}
private int getTimeOut()
{
return time_out;
}
private int[] initPixel()
{
return (pixel = new int[width * height]);
}
private int[] getPixel()
{
return pixel;
}
private void setState(int val)
{
state = val;
}
private int getState()
{
return state;
}
private void clearArea()
{
minX = width;
minY = height;
maxX = maxY = 0;
}
private void addArea(int x, int y, int w, int h)
{
int x2 = x + w;
int y2 = y + h;
int _minX = x < minX ? x : minX;
int _minY = y < minY ? y : minY;
int _maxX = x2 > maxX ? x2 : maxX;
int _maxY = y2 > maxY ? y2 : maxY;
if (_minX < 0 || _minY < 0 || _maxX < 0 || _maxY < 0 || (_maxX - _minX) < 0 || (_maxY - _minY) < 0)
{
Common.setMessage("!> decoding error: page area, page_id " + getId() + "[" + _minX + "," + _minY + "," + _maxX + "," + _maxY + "] (pts " + getTimeIn() + ")");
return;
}
minX = _minX;
minY = _minY;
maxX = _maxX;
maxY = _maxY;
}
private int getX()
{
return minX;
}
private int getY()
{
return minY;
}
private int getWidth()
{
return (maxX - minX);
}
private int getHeight()
{
return (maxY - minY);
}
}
class Region
{
private int id;
private int horizontal_address;
private int vertical_address;
private int version_number = -1;
private boolean fill_flag;
private boolean active = false;
private boolean changed = false;
private int r_width;
private int r_height;
private int x;
private int y;
private int level_of_compatibility;
private int depth;
private int CLUT_id;
private int pixel_code_8bit;
private int pixel_code_4bit;
private int pixel_code_2bit;
private int pixel[];
//DM23062004 081.7 int05 add
private int error = 0;
private Region()
{}
private Region(int val)
{
id = val;
}
private void setId(int val)
{
id = val;
}
private int getId()
{
return id;
}
private void setHorizontalAddress(int val)
{
horizontal_address = val;
x = 0;
}
private void setVerticalAddress(int val)
{
vertical_address = val;
y = 0;
}
private void setVersionNumber(int val) //modulo16, if a change
{
version_number = val;
}
private int getVersionNumber()
{
return version_number;
}
private int[] initPixel()
{
return (pixel = new int[r_width * r_height]);
}
private int[] getPixel()
{
return pixel;
}
private void setFillFlag(int val) // fill background with color of region_pixel_code_xbit index
{
fill_flag = val != 0;
}
private boolean getFillFlag()
{
return fill_flag;
}
private void setActive(boolean b)
{
active = b;
}
private boolean isActive()
{
return active;
}
private void setWidth(int val) //max 720, if h_address =1
{
r_width = val;
}
private int getWidth()
{
return r_width;
}
private void setHeight(int val) //max 576, if v_address =1
{
r_height = val;
}
private int getHeight()
{
return r_height;
}
private void setLevelOfCompatibility(int val) //1,2,3 = 2,4,8bit supported colors by IRD, X can use all :)
{
level_of_compatibility = val;
}
private int getCompatibility()
{
return level_of_compatibility;
}
private void setDepth(int val) //1,2,3 = 2,4,8bit pixel depth
{
depth = 1<<val;
}
private int getDepth()
{
return depth;
}
private void setCLUT_id(int val)
{
CLUT_id = val;
}
private int getCLUT_id()
{
return CLUT_id;
}
private void setPixelCode_8bit(int val) //see fillflag, backgr CLUT entry
{
pixel_code_8bit = val;
}
private void setPixelCode_4bit(int val) //see fillflag, backgr CLUT entry
{
pixel_code_4bit = val;
}
private void setPixelCode_2bit(int val) //see fillflag, backgr CLUT entry
{
pixel_code_2bit = val;
}
private int getPixelCode_8bit()
{
return pixel_code_8bit;
}
private int getPixelCode_4bit()
{
return pixel_code_4bit;
}
private int getPixelCode_2bit()
{
return pixel_code_2bit;
}
private int getXBound()
{
return horizontal_address;
}
private int getYBound()
{
return vertical_address;
}
private int getX(int x_len)
{
int nx = x;
x += x_len;
return nx;
}
private int getY()
{
return y;
}
private void nextLine()
{
x = 0;
y += 2;
}
private void nextField()
{
x = 0;
y = 1;
}
private void resetXY()
{
x = 0;
y = 0;
}
private void setChanged(boolean b)
{
changed = b;
}
private boolean isChanged()
{
return changed;
}
//DM23062004 081.7 int05 add
private int getErrors()
{
return error;
}
//DM23062004 081.7 int05 add
private void setError(int val)
{
error |= val;
if (val == 0)
error = 0;
}
}
class CLUT
{
private int id;
private int version_number = -1;
private int modify_flags = 0;
private final int default_CLUT_2bit[] = {
0, 0xFFFFFFFF, 0xFF000000, 0xFF808080
};
private final int default_CLUT_4bit[] = {
0, 0xFFFF0000, 0xFF00FF00, 0xFFFFFF00, 0xFF0000FF, 0xFFFF00FF,
0xFF00FFFF, 0xFFFFFFFF, 0xFF000000, 0xFF800000, 0xFF008000,
0xFF808000, 0xFF000080, 0xFF800080, 0xFF008080, 0xFF808080
};
private final int default_CLUT_8bit[] = table_CLUT_8bit;
private int CLUT_2bit[];
private int CLUT_4bit[];
private int CLUT_8bit[];
private CLUT()
{
init(0);
}
private CLUT(int val)
{
init(val);
}
private void init(int val)
{
id = val;
CLUT_2bit = default_CLUT_2bit;
CLUT_4bit = default_CLUT_4bit;
CLUT_8bit = default_CLUT_8bit;
}
private void setId(int val)
{
id = val;
}
private int getId()
{
return id;
}
private void setVersionNumber(int val) //modulo16, if a change
{
version_number = val;
}
private int getVersionNumber()
{
return version_number;
}
private int[] getCLUT_2bit()
{
return CLUT_2bit;
}
private int[] getCLUT_4bit()
{
return CLUT_4bit;
}
private int[] getCLUT_8bit()
{
return CLUT_8bit;
}
private void setClutEntry(int index, int flag, int ARGB)
{
if ((flag & 8) != 0)
CLUT_8bit[index] = ARGB;
else if ((flag & 4) != 0)
CLUT_4bit[index] = ARGB;
else if ((flag & 2) != 0)
CLUT_2bit[index] = ARGB;
modify_flags |= flag;
}
private int getModifyFlags()
{
return modify_flags;
}
}
class OBJECT
{
private int id;
private int version_number = -1;
private int type;
private int region_id = -1;
private int provider_flag;
private int horizontal_position;
private int vertical_position;
private int foreground_pixel_code;
private int background_pixel_code;
private boolean non_modifying_color_flag;
private boolean copy_top_field_flag;
private final int default_map_table_2to4bit[] = { 0, 7, 8, 0xF };
private final int default_map_table_2to8bit[] = { 0, 0x77, 0x88, 0xFF };
private final int default_map_table_4to8bit[] = {
0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
private int map_table_2to4bit[];
private int map_table_2to8bit[];
private int map_table_4to8bit[];
private Hashtable regions = new Hashtable();
private OBJECT()
{
init();
}
private OBJECT(int val)
{
id = val;
init();
}
private void init()
{
map_table_2to4bit = default_map_table_2to4bit;
map_table_2to8bit = default_map_table_2to8bit;
map_table_4to8bit = default_map_table_4to8bit;
}
private void setId(int val)
{
id = val;
}
private int getId()
{
return id;
}
private void setType(int val)
{
type = val;
}
private int getType() //0 bitmap,1 char, 2 chars
{
return type;
}
private void setProvider(int val) //0 def. in stream, 1 read from ROM, 2+3 res.
{
provider_flag = val;
}
private void setHorizontalPosition(int val)
{
horizontal_position = val;
}
private int getHorizontalPosition()
{
return horizontal_position;
}
private void setVerticalPosition(int val)
{
vertical_position = val;
}
private int getVerticalPosition()
{
return vertical_position;
}
private void setForegroundCode(int val) // color entry in 8bit CLUT
{
foreground_pixel_code = val;
}
private void setBackgroundCode(int val) // color entry in 8bit CLUT
{
background_pixel_code = val;
}
private void setVersionNumber(int val) //modulo16, if a change
{
version_number = val;
}
private int getVersionNumber()
{
return version_number;
}
private void setNonModify(int val) // pixel with CLUT color entry 1 = dont overwrite it
{
non_modifying_color_flag = val != 0;
}
private void setCopyTopFieldFlag(int val) // bottomfield isn't transmitted, so copy topfield
{
copy_top_field_flag = val == 0;
}
private boolean getCopyTopFieldFlag()
{
return copy_top_field_flag;
}
private void setMapTable_2to4bit(int i, int val)
{
map_table_2to4bit[i] = val;
}
private void setMapTable_2to8bit(int i, int val)
{
map_table_2to8bit[i] = val;
}
private void setMapTable_4to8bit(int i, int val)
{
map_table_4to8bit[i] = val;
}
private int[] getMapTable_2to4bit()
{
return map_table_2to4bit;
}
private int[] getMapTable_2to8bit()
{
return map_table_2to8bit;
}
private int[] getMapTable_4to8bit()
{
return map_table_4to8bit;
}
private void setRegionId(int val)
{
region_id = val;
}
private int getRegionId()
{
return region_id;
}
private void resetRegionId()
{
region_id = -1;
}
}
}