Initial commit
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
build
|
||||
dist
|
||||
compile.sh
|
||||
.vscode
|
4
META-INF/MANIFEST.MF
Normal file
|
@ -0,0 +1,4 @@
|
|||
Manifest-Version: 1.0
|
||||
Class-Path: .
|
||||
Main-Class: TileMolester
|
||||
|
13
TileMolester.iml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/languages" type="java-resource" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
BIN
TileMolester.jar
Normal file
215
languages/language.properties
Normal file
|
@ -0,0 +1,215 @@
|
|||
################# Default English translation by Kent Hansen ##################
|
||||
|
||||
# File Menu
|
||||
|
||||
File = File
|
||||
New = New...
|
||||
Open = Open...
|
||||
Reopen = Reopen
|
||||
Close = Close
|
||||
Close_All = Close All
|
||||
Save = Save
|
||||
Save_As = Save As...
|
||||
Save_All = Save All
|
||||
Exit = Exit
|
||||
|
||||
# View Menu
|
||||
|
||||
View = View
|
||||
Statusbar = Statusbar
|
||||
Toolbar = Toolbar
|
||||
Codec = Codec
|
||||
Zoom = Zoom
|
||||
In = In
|
||||
Out = Out
|
||||
1_Dimensional = 1-Dimensional
|
||||
2_Dimensional = 2-Dimensional
|
||||
Block_Size = Block Size
|
||||
Full_Canvas = Full Canvas
|
||||
Custom_Block_Size = Custom...
|
||||
Row_Interleave_Blocks = Row-interleaved
|
||||
Block_Grid = Block Grid
|
||||
Tile_Grid = Tile Grid
|
||||
Pixel_Grid = Pixel Grid
|
||||
|
||||
# Edit Menu
|
||||
|
||||
Edit = Edit
|
||||
Undo = Undo
|
||||
Redo = Redo
|
||||
Cant_Undo = Can't Undo
|
||||
Cant_Redo = Can't Redo
|
||||
Cut = Cut
|
||||
Copy = Copy
|
||||
Paste = Paste
|
||||
Clear = Clear
|
||||
Select_All = Select All
|
||||
Copy_To = Copy To...
|
||||
Paste_From = Paste From...
|
||||
Apply_Selection = Apply Selection
|
||||
New_Selection = New Selection
|
||||
|
||||
# Image Menu
|
||||
|
||||
Image = Image
|
||||
Mirror = Mirror
|
||||
Flip = Flip
|
||||
Rotate_Right = Rotate Right
|
||||
Rotate_Left = Rotate Left
|
||||
Shift_Left = Shift Left
|
||||
Shift_Right = Shift Right
|
||||
Shift_Up = Shift Up
|
||||
Shift_Down = Shift Down
|
||||
Stretch = Stretch...
|
||||
Canvas_Size = Canvas Size...
|
||||
|
||||
# Navigate Menu
|
||||
|
||||
Navigate = Navigate
|
||||
Go_To = Go To...
|
||||
Go_To_Again = Go To Again
|
||||
Add_To_Bookmarks = Add To Bookmarks...
|
||||
Organize_Bookmarks = Organize Bookmarks...
|
||||
Bookmarks = Bookmarks
|
||||
|
||||
# Palette menu
|
||||
|
||||
Palette = Palette
|
||||
Edit_Colors = Edit Colors...
|
||||
Format = Format
|
||||
Size = Size...
|
||||
Import_From = Import From
|
||||
This_File = This File...
|
||||
Another_File = Another File...
|
||||
Add_To_Palettes = Add To Palettes...
|
||||
Organize_Palettes = Organize Palettes...
|
||||
|
||||
# Window Menu
|
||||
|
||||
Window = Window
|
||||
New_Window = New Window
|
||||
Tile = Tile
|
||||
Cascade = Cascade
|
||||
Arrange_Icons = Arrange Icons
|
||||
|
||||
# Help Menu
|
||||
|
||||
Help = Help
|
||||
Help_Topics = Help Topics
|
||||
Tip_of_the_Millennium = Tip of the Millennium...
|
||||
About_Tile_Molester = About Tile Molester
|
||||
|
||||
# Tools
|
||||
|
||||
Selection = Selection
|
||||
Dropper = Dropper
|
||||
Brush = Brush
|
||||
Line = Line
|
||||
Flood_Fill = Flood Fill
|
||||
Color_Replacer = Color Replacer
|
||||
Mover = Mover
|
||||
|
||||
# Navigation button tooltips
|
||||
|
||||
Page_Back = Page Back
|
||||
Page_Forward = Page Forward
|
||||
Row_Back = Row Back
|
||||
Row_Forward = Row Forward
|
||||
Tile_Back = Tile Back
|
||||
Tile_Forward = Tile Forward
|
||||
Byte_Back = Byte Back
|
||||
Byte_Forward = Byte Forward
|
||||
|
||||
# Messages
|
||||
|
||||
Save_Changes_To = Save changes to
|
||||
Load_File_Error = Error loading file:
|
||||
Save_File_Error = Error saving file:
|
||||
Load_Bitmap_Error = Error loading bitmap:
|
||||
Save_Bitmap_Error = Error saving bitmap:
|
||||
Save_Settings_Error = Error saving settings:
|
||||
Load_Settings_Error = Error loading settings:
|
||||
Save_Resources_Error = Error saving resources:
|
||||
Load_Resources_Error = Error loading resources:
|
||||
File_Write_Error = Can't write to file:
|
||||
Palette_Read_Error = Error reading palette data:
|
||||
Parser_Config_Error = XML parser configuration error:
|
||||
Parser_Parse_Error = Error parsing XML:
|
||||
Parser_IO_Error = XML parser IO error:
|
||||
Out_Of_Memory = Out of memory.
|
||||
Drugs_Message = Stay off the drugs, OK?
|
||||
|
||||
# Built-in File Filter Names
|
||||
|
||||
All_Supported_Formats = All Supported Formats
|
||||
All_Files = All Files (*.*)
|
||||
|
||||
# Bookmark/palette organization
|
||||
|
||||
Create_In = Create In:
|
||||
New_Folder = New Folder...
|
||||
Rename = Rename
|
||||
Move = Move
|
||||
Delete = Delete
|
||||
|
||||
# Confirmation button captions
|
||||
|
||||
Yes = Yes
|
||||
No = No
|
||||
OK = OK
|
||||
Cancel = Cancel
|
||||
|
||||
# Endianness
|
||||
|
||||
Endianness = Byte Order
|
||||
Little_Endian = Intel
|
||||
Big_Endian = Motorola
|
||||
|
||||
# Dialog titles
|
||||
|
||||
New_File_Dialog_Title = Create New File
|
||||
New_Folder_Dialog_Title = Create New Folder
|
||||
Open_File_Dialog_Title = Open
|
||||
Save_As_Dialog_Title = Save As
|
||||
Copy_To_Dialog_Title = Copy To
|
||||
Paste_From_Dialog_Title = Paste From
|
||||
Go_To_Dialog_Title = Go To Offset
|
||||
Stretch_Image_Dialog_Title = Stretch Image
|
||||
Block_Size_Dialog_Title = Set Block Size
|
||||
Canvas_Size_Dialog_Title = Canvas Size
|
||||
Add_To_Bookmarks_Dialog_Title = Add To Bookmarks
|
||||
Organize_Bookmarks_Dialog_Title = Organize Bookmarks
|
||||
Add_To_Palettes_Dialog_Title = Add To Palettes
|
||||
Organize_Palettes_Dialog_Title = Organize Palettes
|
||||
Edit_Colors_Dialog_Title = Edit Colors
|
||||
Palette_Size_Dialog_Title = Change Palette Size
|
||||
New_Palette_Dialog_Title = Create New Palette
|
||||
Import_Internal_Palette_Dialog_Title = Import Internal Palette
|
||||
Open_Palette_Dialog_Title = Import External Palette
|
||||
|
||||
# Incremental Canvas Size
|
||||
|
||||
Decrease_Width = Decrease Width
|
||||
Increase_Width = Increase Width
|
||||
Decrease_Height = Decrease Height
|
||||
Increase_Height = Increase Height
|
||||
|
||||
# Input field labels
|
||||
|
||||
Folder_Name_Prompt = Folder Name:
|
||||
Description_Prompt = Description:
|
||||
Offset_Prompt = Offset:
|
||||
Size_Prompt = Size:
|
||||
Columns_Prompt = Columns:
|
||||
Rows_Prompt = Rows:
|
||||
|
||||
# Other
|
||||
|
||||
Empty = Empty
|
||||
Offset = Offset
|
||||
Mode = Mode
|
||||
Radix = Radix
|
||||
Hex = Hex
|
||||
Dec = Dec
|
||||
Absolute = Absolute
|
||||
Relative = Relative
|
215
languages/language_en_US.properties
Normal file
|
@ -0,0 +1,215 @@
|
|||
################# Default English translation by Kent Hansen ##################
|
||||
|
||||
# File Menu
|
||||
|
||||
File = File
|
||||
New = New...
|
||||
Open = Open...
|
||||
Reopen = Reopen
|
||||
Close = Close
|
||||
Close_All = Close All
|
||||
Save = Save
|
||||
Save_As = Save As...
|
||||
Save_All = Save All
|
||||
Exit = Exit
|
||||
|
||||
# View Menu
|
||||
|
||||
View = View
|
||||
Statusbar = Statusbar
|
||||
Toolbar = Toolbar
|
||||
Codec = Codec
|
||||
Zoom = Zoom
|
||||
In = In
|
||||
Out = Out
|
||||
1_Dimensional = 1-Dimensional
|
||||
2_Dimensional = 2-Dimensional
|
||||
Block_Size = Block Size
|
||||
Full_Canvas = Full Canvas
|
||||
Custom_Block_Size = Custom...
|
||||
Row_Interleave_Blocks = Row-interleaved
|
||||
Block_Grid = Block Grid
|
||||
Tile_Grid = Tile Grid
|
||||
Pixel_Grid = Pixel Grid
|
||||
|
||||
# Edit Menu
|
||||
|
||||
Edit = Edit
|
||||
Undo = Undo
|
||||
Redo = Redo
|
||||
Cant_Undo = Can't Undo
|
||||
Cant_Redo = Can't Redo
|
||||
Cut = Cut
|
||||
Copy = Copy
|
||||
Paste = Paste
|
||||
Clear = Clear
|
||||
Select_All = Select All
|
||||
Copy_To = Copy To...
|
||||
Paste_From = Paste From...
|
||||
Apply_Selection = Apply Selection
|
||||
New_Selection = New Selection
|
||||
|
||||
# Image Menu
|
||||
|
||||
Image = Image
|
||||
Mirror = Mirror
|
||||
Flip = Flip
|
||||
Rotate_Right = Rotate Right
|
||||
Rotate_Left = Rotate Left
|
||||
Shift_Left = Shift Left
|
||||
Shift_Right = Shift Right
|
||||
Shift_Up = Shift Up
|
||||
Shift_Down = Shift Down
|
||||
Stretch = Stretch...
|
||||
Canvas_Size = Canvas Size...
|
||||
|
||||
# Navigate Menu
|
||||
|
||||
Navigate = Navigate
|
||||
Go_To = Go To...
|
||||
Go_To_Again = Go To Again
|
||||
Add_To_Bookmarks = Add To Bookmarks...
|
||||
Organize_Bookmarks = Organize Bookmarks...
|
||||
Bookmarks = Bookmarks
|
||||
|
||||
# Palette menu
|
||||
|
||||
Palette = Palette
|
||||
Edit_Colors = Edit Colors...
|
||||
Format = Format
|
||||
Size = Size...
|
||||
Import_From = Import From
|
||||
This_File = This File...
|
||||
Another_File = Another File...
|
||||
Add_To_Palettes = Add To Palettes...
|
||||
Organize_Palettes = Organize Palettes...
|
||||
|
||||
# Window Menu
|
||||
|
||||
Window = Window
|
||||
New_Window = New Window
|
||||
Tile = Tile
|
||||
Cascade = Cascade
|
||||
Arrange_Icons = Arrange Icons
|
||||
|
||||
# Help Menu
|
||||
|
||||
Help = Help
|
||||
Help_Topics = Help Topics
|
||||
Tip_of_the_Millennium = Tip of the Millennium...
|
||||
About_Tile_Molester = About Tile Molester
|
||||
|
||||
# Tools
|
||||
|
||||
Selection = Selection
|
||||
Dropper = Dropper
|
||||
Brush = Brush
|
||||
Line = Line
|
||||
Flood_Fill = Flood Fill
|
||||
Color_Replacer = Color Replacer
|
||||
Mover = Mover
|
||||
|
||||
# Navigation button tooltips
|
||||
|
||||
Page_Back = Page Back
|
||||
Page_Forward = Page Forward
|
||||
Row_Back = Row Back
|
||||
Row_Forward = Row Forward
|
||||
Tile_Back = Tile Back
|
||||
Tile_Forward = Tile Forward
|
||||
Byte_Back = Byte Back
|
||||
Byte_Forward = Byte Forward
|
||||
|
||||
# Messages
|
||||
|
||||
Save_Changes_To = Save changes to
|
||||
Load_File_Error = Error loading file:
|
||||
Save_File_Error = Error saving file:
|
||||
Load_Bitmap_Error = Error loading bitmap:
|
||||
Save_Bitmap_Error = Error saving bitmap:
|
||||
Save_Settings_Error = Error saving settings:
|
||||
Load_Settings_Error = Error loading settings:
|
||||
Save_Resources_Error = Error saving resources:
|
||||
Load_Resources_Error = Error loading resources:
|
||||
File_Write_Error = Can't write to file:
|
||||
Palette_Read_Error = Error reading palette data:
|
||||
Parser_Config_Error = XML parser configuration error:
|
||||
Parser_Parse_Error = Error parsing XML:
|
||||
Parser_IO_Error = XML parser IO error:
|
||||
Out_Of_Memory = Out of memory.
|
||||
Drugs_Message = Stay off the drugs, OK?
|
||||
|
||||
# Built-in File Filter Names
|
||||
|
||||
All_Supported_Formats = All Supported Formats
|
||||
All_Files = All Files (*.*)
|
||||
|
||||
# Bookmark/palette organization
|
||||
|
||||
Create_In = Create In:
|
||||
New_Folder = New Folder...
|
||||
Rename = Rename
|
||||
Move = Move
|
||||
Delete = Delete
|
||||
|
||||
# Confirmation button captions
|
||||
|
||||
Yes = Yes
|
||||
No = No
|
||||
OK = OK
|
||||
Cancel = Cancel
|
||||
|
||||
# Endianness
|
||||
|
||||
Endianness = Byte Order
|
||||
Little_Endian = Intel
|
||||
Big_Endian = Motorola
|
||||
|
||||
# Dialog titles
|
||||
|
||||
New_File_Dialog_Title = Create New File
|
||||
New_Folder_Dialog_Title = Create New Folder
|
||||
Open_File_Dialog_Title = Open
|
||||
Save_As_Dialog_Title = Save As
|
||||
Copy_To_Dialog_Title = Copy To
|
||||
Paste_From_Dialog_Title = Paste From
|
||||
Go_To_Dialog_Title = Go To Offset
|
||||
Stretch_Image_Dialog_Title = Stretch Image
|
||||
Block_Size_Dialog_Title = Set Block Size
|
||||
Canvas_Size_Dialog_Title = Canvas Size
|
||||
Add_To_Bookmarks_Dialog_Title = Add To Bookmarks
|
||||
Organize_Bookmarks_Dialog_Title = Organize Bookmarks
|
||||
Add_To_Palettes_Dialog_Title = Add To Palettes
|
||||
Organize_Palettes_Dialog_Title = Organize Palettes
|
||||
Edit_Colors_Dialog_Title = Edit Colors
|
||||
Palette_Size_Dialog_Title = Change Palette Size
|
||||
New_Palette_Dialog_Title = Create New Palette
|
||||
Import_Internal_Palette_Dialog_Title = Import Internal Palette
|
||||
Open_Palette_Dialog_Title = Import External Palette
|
||||
|
||||
# Incremental Canvas Size
|
||||
|
||||
Decrease_Width = Decrease Width
|
||||
Increase_Width = Increase Width
|
||||
Decrease_Height = Decrease Height
|
||||
Increase_Height = Increase Height
|
||||
|
||||
# Input field labels
|
||||
|
||||
Folder_Name_Prompt = Folder Name:
|
||||
Description_Prompt = Description:
|
||||
Offset_Prompt = Offset:
|
||||
Size_Prompt = Size:
|
||||
Columns_Prompt = Columns:
|
||||
Rows_Prompt = Rows:
|
||||
|
||||
# Other
|
||||
|
||||
Empty = Empty
|
||||
Offset = Offset
|
||||
Mode = Mode
|
||||
Radix = Radix
|
||||
Hex = Hex
|
||||
Dec = Dec
|
||||
Absolute = Absolute
|
||||
Relative = Relative
|
215
languages/language_it_IT.properties
Normal file
|
@ -0,0 +1,215 @@
|
|||
#################### Traduzione in Italiano da Gabriele "Karvek" De Luca #####################
|
||||
|
||||
# File Menu
|
||||
|
||||
File = File
|
||||
New = Nuovo...
|
||||
Open = Apri...
|
||||
Reopen = Riapri
|
||||
Close = Chiudi
|
||||
Close_All = Chiudi tutti
|
||||
Save = Salva
|
||||
Save_As = Salva come...
|
||||
Save_All = Salva tutti
|
||||
Exit = Esci
|
||||
|
||||
# View Menu
|
||||
|
||||
View = View
|
||||
Statusbar = Barra di Stato
|
||||
Toolbar = Barra degli Strumenti
|
||||
Codec = Codifica
|
||||
Zoom = Zoom
|
||||
In = Dentro
|
||||
Out = Fuori
|
||||
1_Dimensional = 1 Dimensione
|
||||
2_Dimensional = 2 Dimensioni
|
||||
Block_Size = Dimensione Blocchi
|
||||
Full_Canvas = Tutto il Canvas
|
||||
Custom_Block_Size = Dimensione Blocchi Personalizzata
|
||||
Row_Interleave_Blocks = Blocchi Rigati
|
||||
Block_Grid = Griglia Blocchi
|
||||
Tile_Grid = Griglia Tile
|
||||
Pixel_Grid = Grigilia Pixel
|
||||
|
||||
# Edit Menu
|
||||
|
||||
Edit = Modifica
|
||||
Undo = Annulla
|
||||
Redo = Ripeti
|
||||
Cant_Undo = Impossibile annullare
|
||||
Cant_Redo = Impossibile ripetere
|
||||
Cut = Taglia
|
||||
Copy = Copia
|
||||
Paste = Incolla
|
||||
Clear = Elimina
|
||||
Select_All = Seleziona tutto
|
||||
Copy_To = Copia a...
|
||||
Paste_From = Incolla da...
|
||||
Apply_Selection = Applica selezione
|
||||
New_Selection = Nuova selezione
|
||||
|
||||
# Image Menu
|
||||
|
||||
Image = Immagine
|
||||
Mirror = Specchio Orizzontale
|
||||
Flip = Specchio Verticale
|
||||
Rotate_Right = 90° a destra
|
||||
Rotate_Left = 90° a sinistra
|
||||
Shift_Left = Pixel a sinistra
|
||||
Shift_Right = Pixel a destra
|
||||
Shift_Up = Pixel in su
|
||||
Shift_Down = Pixel in giu
|
||||
Stretch = Ridimensiona
|
||||
Canvas_Size = Dimensione Canvas
|
||||
|
||||
# Navigate Menu
|
||||
|
||||
Navigate = Navigator
|
||||
Go_To = Vai a...
|
||||
Go_To_Again = Vai di nuovo a
|
||||
Add_To_Bookmarks = Aggiungi a Preferiti...
|
||||
Organize_Bookmarks = Organizza i Preferiti...
|
||||
Bookmarks = Preferiti
|
||||
|
||||
# Palette menu
|
||||
|
||||
Palette = Palette
|
||||
Edit_Colors = Modifica Colori...
|
||||
Format = Formato
|
||||
Size = Dimensioni
|
||||
Import_From = Importa da...
|
||||
This_File = Questo File...
|
||||
Another_File = Un altro File..
|
||||
Add_To_Palettes = Aggiungi a Palette...
|
||||
Organize_Palettes = Organizza Palette...
|
||||
|
||||
# Window Menu
|
||||
|
||||
Window = Finestre
|
||||
New_Window = Nuova Finestra
|
||||
Tile = Tutto lo Spazio
|
||||
Cascade = Di Seguito
|
||||
Arrange_Icons = Ordina Icone
|
||||
|
||||
# Help Menu
|
||||
|
||||
Help = Aiuto
|
||||
Help_Topics = Guida
|
||||
Tip_of_the_Millennium = Suggerimento
|
||||
About_Tile_Molester = About
|
||||
|
||||
# Tools
|
||||
|
||||
Selection = Selection
|
||||
Dropper = Dropper
|
||||
Brush = Brush
|
||||
Line = Line
|
||||
Flood_Fill = Flodd Fill
|
||||
Color_Replacer = Color Replacer
|
||||
Mover = Mover
|
||||
|
||||
# Navigation button tooltips
|
||||
|
||||
Page_Back = Pagina Indietro
|
||||
Page_Forward = Pagina Avanti
|
||||
Row_Back = Riga Indietro
|
||||
Row_Forward = Riga Avanti
|
||||
Tile_Back = Tile Indietro
|
||||
Tile_Forward = Tile Avanti
|
||||
Byte_Back = Byte Indietro
|
||||
Byte_Forward = Byte Avanti
|
||||
|
||||
# Messages
|
||||
|
||||
Save_Changes_To = Salva modifiche a
|
||||
Load_File_Error = Impossibile aprire file:
|
||||
Save_File_Error = Impossibile salvare file:
|
||||
Load_Bitmap_Error = Impossibile aprire bitmap:
|
||||
Save_Bitmap_Error = Impossibile salvare bitmap:
|
||||
Save_Settings_Error = Impossibile salvare impostazioni:
|
||||
Load_Settings_Error = Impossibile caricare impostazioni:
|
||||
Save_Resources_Error = Impossibile salvare risorsa:
|
||||
Load_Resources_Error = Impossibile caricare risorsa:
|
||||
File_Write_Error = Impossibile scrivere su file:
|
||||
Palette_Read_Error = Impossibile leggere palette:
|
||||
Parser_Config_Error = Errore di configurazione XML:
|
||||
Parser_Parse_Error = Errore di parsing XML:
|
||||
Parser_IO_Error = Errore I/O di XML:
|
||||
Out_Of_Memory = Fuori limite memoria
|
||||
Drugs_Message = Non utilizzare droghe, OK?
|
||||
|
||||
# Built-in File Filter Names
|
||||
|
||||
All_Supported_Formats = Tutti i file supportati
|
||||
All_Files = Tutti i files (*.*)
|
||||
|
||||
# Bookmark/palette organization
|
||||
|
||||
Create_In = Crea
|
||||
New_Folder = Nuova Cartella
|
||||
Rename = Rinomina
|
||||
Move = Sposta
|
||||
Delete = Cancella
|
||||
|
||||
# Confirmation button captions
|
||||
|
||||
Yes = Si
|
||||
No = No
|
||||
OK = Ok
|
||||
Cancel = Cancella
|
||||
|
||||
# Endianness
|
||||
|
||||
Endianness = Ordine di Byte
|
||||
Little_Endian = Intel
|
||||
Big_Endian = Motorola
|
||||
|
||||
# Dialog titles
|
||||
|
||||
New_File_Dialog_Title = Crea nuovo file
|
||||
New_Folder_Dialog_Title = Crea nuova cartella
|
||||
Open_File_Dialog_Title = Apri
|
||||
Save_As_Dialog_Title = Salva come
|
||||
Copy_To_Dialog_Title = Copia a
|
||||
Paste_From_Dialog_Title = Incolla da
|
||||
Go_To_Dialog_Title = Vai a offset
|
||||
Stretch_Image_Dialog_Title = Ridimensiona Immagine
|
||||
Canvas_Size_Dialog_Title = Modifica Dimensione Blocchi
|
||||
Block_Size_Dialog_Title = Dimensione Canvas
|
||||
Add_To_Bookmarks_Dialog_Title = Aggiungi a Preferiti
|
||||
Organize_Bookmarks_Dialog_Title = Organizza Preferiti
|
||||
Add_To_Palettes_Dialog_Title = Aggiungi a Palette
|
||||
Organize_Palettes_Dialog_Title = Organizza Palette
|
||||
Edit_Colors_Dialog_Title = Modifica Colori
|
||||
Palette_Size_Dialog_Title = Cambia Dimensione Palette
|
||||
New_Palette_Dialog_Title = Crea Nuova Palette
|
||||
Import_Internal_Palette_Dialog_Title = Importa Palette Interna
|
||||
Open_Palette_Dialog_Title = Importa Palette Esterna
|
||||
|
||||
# Incremental Canvas Size
|
||||
|
||||
Decrease_Width = Diminuisci Lunghezza
|
||||
Increase_Width = Aumenta Lunghezza
|
||||
Decrease_Height = Diminuisci Altezza
|
||||
Increase_Height = Aumenta Altezza
|
||||
|
||||
# Input field labels
|
||||
|
||||
Folder_Name_Prompt = Nome Cartella:
|
||||
Description_Prompt = Descrizione:
|
||||
Offset_Prompt = Offset:
|
||||
Size_Prompt = Dimensioni:
|
||||
Columns_Prompt = Colonne:
|
||||
Rows_Prompt = Righe:
|
||||
|
||||
# Other
|
||||
|
||||
Empty = Vuoto
|
||||
Offset = Offset
|
||||
Mode = Modalità
|
||||
Radix = Radix
|
||||
Hex = Esa
|
||||
Dec = Dec
|
||||
Absolute = Assoluto
|
||||
Relative = Relativo
|
216
languages/language_no_NO.properties
Normal file
|
@ -0,0 +1,216 @@
|
|||
###################### Norsk oversettelse av Kent Hansen ######################
|
||||
|
||||
# Filmeny
|
||||
|
||||
File = Fil
|
||||
New = Ny...
|
||||
Open = Åpne...
|
||||
Reopen = Gjenåpne
|
||||
Close = Lukk
|
||||
Close_All = Lukk Alle
|
||||
Save = Lagre
|
||||
Save_As = Lagre Som...
|
||||
Save_All = Lagre Alle
|
||||
Exit = Avslutt
|
||||
|
||||
# Redigeringsmeny
|
||||
|
||||
Edit = Rediger
|
||||
Undo = Angre
|
||||
Redo = Gjenta
|
||||
Cant_Undo = Kan Ikke Angre
|
||||
Cant_Redo = Kan Ikke Gjenta
|
||||
Cut = Klipp Ut
|
||||
Copy = Kopier
|
||||
Paste = Lim Inn
|
||||
Clear = Fjern
|
||||
Select_All = Merk Alt
|
||||
Copy_To = Kopier Til...
|
||||
Paste_From = Lim Inn Fra...
|
||||
Apply_Selection = Apply Selection
|
||||
New_Selection = New Selection
|
||||
|
||||
#Vis-meny
|
||||
|
||||
View = Vis
|
||||
Statusbar = Statuslinje
|
||||
Toolbar = Verktøylinje
|
||||
Codec = Kodek
|
||||
Zoom = Zoom
|
||||
In = Inn
|
||||
Out = Ut
|
||||
1_Dimensional = 1-Dimensjonell
|
||||
2_Dimensional = 2-Dimensjonell
|
||||
Block_Size = Blokk-størrelse
|
||||
Full_Canvas = Hele Bildet
|
||||
Custom_Block_Size = Egenvalgt...
|
||||
Row_Interleave_Blocks = Rad-flettet
|
||||
Block_Grid = Blokk-rutenett
|
||||
Tile_Grid = Flis-rutenett
|
||||
Pixel_Grid = Piksel-rutenett
|
||||
|
||||
# Bildemeny
|
||||
|
||||
Image = Bilde
|
||||
Mirror = Vend Vannrett
|
||||
Flip = Vend Loddrett
|
||||
Rotate_Right = Rotér Mot Høyre
|
||||
Rotate_Left = Rotér Mot Venstre
|
||||
Shift_Left = Skift Til Venstre
|
||||
Shift_Right = Skift Til Høyre
|
||||
Shift_Up = Skift Opp
|
||||
Shift_Down = Skift Ned
|
||||
Stretch = Strekk...
|
||||
Canvas_Size = Canvas Size...
|
||||
|
||||
# Navigeringsmeny
|
||||
|
||||
Navigate = Navigér
|
||||
Go_To = Gå Til...
|
||||
Go_To_Again = Gå Til Igjen
|
||||
Add_To_Bookmarks = Legg Til i Bokmerker...
|
||||
Organize_Bookmarks = Organisér Bokmerker...
|
||||
Bookmarks = Bokmerker
|
||||
|
||||
# Palettmeny
|
||||
|
||||
Palette = Palett
|
||||
Edit_Colors = Redigér Farger...
|
||||
Format = Format
|
||||
Size = Størrelse...
|
||||
Import_From = Importér Fra
|
||||
This_File = Denne Filen...
|
||||
Another_File = Annen Fil...
|
||||
Add_To_Palettes = Legg Til i Paletter...
|
||||
Organize_Palettes = Organisér Paletter...
|
||||
|
||||
# Vindusmeny
|
||||
|
||||
Window = Vindu
|
||||
New_Window = Nytt Vindu
|
||||
Tile = Flislegg
|
||||
Cascade = Strøm
|
||||
Arrange_Icons = Ordne Ikoner
|
||||
|
||||
# Hjelp-meny
|
||||
|
||||
Help = Hjelp
|
||||
Help_Topics = Emner i Hjelp...
|
||||
Tip_of_the_Millennium = Millenniets Tips...
|
||||
About_Tile_Molester = Om Tile Molester...
|
||||
|
||||
# Verktøy
|
||||
|
||||
Selection = Utvalg
|
||||
Dropper = Hent Farge
|
||||
Brush = Pensel
|
||||
Line = Linje
|
||||
Flood_Fill = Fyll Med Farge
|
||||
Color_Replacer = Erstatt Farge
|
||||
Mover = Flytt Fokus
|
||||
|
||||
# Navigation button tooltips
|
||||
|
||||
Page_Back = Side Tilbake
|
||||
Page_Forward = Side Framover
|
||||
Row_Back = Rad Tilbake
|
||||
Row_Forward = Rad Framover
|
||||
Tile_Back = Tile Tilbake
|
||||
Tile_Forward = Tile Framover
|
||||
Byte_Back = Byte Tilbake
|
||||
Byte_Forward = Byte Framover
|
||||
|
||||
# Beskjeder
|
||||
|
||||
Save_Changes_To = Lagre endringer i
|
||||
Load_File_Error = Feil ved lasting av fil:
|
||||
Save_File_Error = Feil ved lagring av fil:
|
||||
File_Write_Error = Kan ikke skrive til fil:
|
||||
Palette_Read_Error = Feil ved lesing av palettdata:
|
||||
Load_Bitmap_Error = Feil ved lasting av bildefil:
|
||||
Save_Bitmap_Error = Feil ved lagring av bildefil:
|
||||
Parser_Config_Error = XML parser konfigurasjonsfeil:
|
||||
Parser_Parse_Error = XML parsefeil:
|
||||
Parser_IO_Error = XML parser IO feil:
|
||||
Save_Settings_Error = Feil ved lagring av innstillinger:
|
||||
Load_Settings_Error = Feil ved lasting av innstillinger:
|
||||
Save_Resources_Error = Feil ved lagring av filressurser:
|
||||
Load_Resources_Error = Feil ved lasting av filressurser:
|
||||
Out_Of_Memory = Tom for minne.
|
||||
Drugs_Message = Hold deg unna dop, okei?
|
||||
|
||||
# Innebygde filfilternavn
|
||||
|
||||
All_Supported_Formats = Alle Støttede Filtyper
|
||||
All_Files = Alle Filer (*.*)
|
||||
|
||||
# Bokmerke/palette-organisering
|
||||
|
||||
Create_In = Lagre i:
|
||||
New_Folder = Ny Mappe...
|
||||
|
||||
Rename = Gi nytt navn
|
||||
Move = Flytt
|
||||
Delete = Slett
|
||||
|
||||
# Confirmation button captions
|
||||
|
||||
Yes = Ja
|
||||
No = Nei
|
||||
OK = OK
|
||||
Cancel = Avbryt
|
||||
|
||||
# Endianness
|
||||
|
||||
Endianness = Byte Rekkefølge
|
||||
Little_Endian = Intel
|
||||
Big_Endian = Motorola
|
||||
|
||||
# Dialog titles
|
||||
|
||||
New_File_Dialog_Title = Opprett Ny Fil
|
||||
New_Folder_Dialog_Title = Opprett Ny Mappe
|
||||
Open_File_Dialog_Title = Åpne Fil
|
||||
Save_As_Dialog_Title = Lagre Som
|
||||
Copy_To_Dialog_Title = Kopier Til
|
||||
Paste_From_Dialog_Title = Lim Inn Fra
|
||||
Go_To_Dialog_Title = Gå Til Filposisjon
|
||||
Stretch_Image_Dialog_Title = Strekk Bilde
|
||||
Block_Size_Dialog_Title = Sett Blokk-størrelse
|
||||
Canvas_Size_Dialog_Title = Canvas Size
|
||||
Add_To_Bookmarks_Dialog_Title = Legg Til i Bokmerker
|
||||
Organize_Bookmarks_Dialog_Title = Organisér Bokmerker
|
||||
Add_To_Palettes_Dialog_Title = Legg Til i Paletter
|
||||
Organize_Palettes_Dialog_Title = Organisér Paletter
|
||||
Edit_Colors_Dialog_Title = Redigér Farger
|
||||
Palette_Size_Dialog_Title = Endre Palettstørrelse
|
||||
New_Palette_Dialog_Title = Opprett Ny Palett
|
||||
Import_Internal_Palette_Dialog_Title = Importér Intern Palett
|
||||
Open_Palette_Dialog_Title = Importér Ekstern Palett
|
||||
|
||||
# Inkrementell Canvas Size
|
||||
|
||||
Decrease_Width = Mink Bredde
|
||||
Increase_Width = Øk Bredde
|
||||
Decrease_Height = Mink Høyde
|
||||
Increase_Height = Øk Høyde
|
||||
|
||||
# Input-felter
|
||||
|
||||
Folder_Name_Prompt = Mappenavn:
|
||||
Description_Prompt = Description:
|
||||
Offset_Prompt = Posisjon:
|
||||
Size_Prompt = Størrelse:
|
||||
Columns_Prompt = Kolonner:
|
||||
Rows_Prompt = Rader:
|
||||
|
||||
# Andre
|
||||
|
||||
Empty = Tom
|
||||
Offset = Posisjon
|
||||
Mode = Modus
|
||||
Radix = Tallbase
|
||||
Hex = Heks.
|
||||
Dec = Des.
|
||||
Absolute = Absolutt
|
||||
Relative = Relativ
|
215
languages/language_sp_LA.properties
Normal file
|
@ -0,0 +1,215 @@
|
|||
################# Traducción al Español por Magimaster 2 (Billy Esquivel) ##################
|
||||
|
||||
# File Menu
|
||||
|
||||
File = Archivo
|
||||
New = Nuevo...
|
||||
Open = Abrir...
|
||||
Reopen = Volver a Abrir
|
||||
Close = Cerrar
|
||||
Close_All = Cerrar Todo
|
||||
Save = Salvar
|
||||
Save_As = Salvar Como...
|
||||
Save_All = Salvar Todo
|
||||
Exit = Salir
|
||||
|
||||
# View Menu
|
||||
|
||||
View = Ver
|
||||
Statusbar = Barra de Estado
|
||||
Toolbar = Barra de Herramientas
|
||||
Codec = Codec
|
||||
Zoom = Acercamiento
|
||||
In = Cerca
|
||||
Out = Lejos
|
||||
1_Dimensional = 1 Dimensión
|
||||
2_Dimensional = 2 Dimensiones
|
||||
Block_Size = Tamaño del Bloque
|
||||
Full_Canvas = Cuadro Completo
|
||||
Custom_Block_Size = Definir...
|
||||
Row_Interleave_Blocks = Fila-Entrelazado
|
||||
Block_Grid = Maya en Bloques
|
||||
Tile_Grid = Maya en Mosaicos
|
||||
Pixel_Grid = Maya en Pixeles
|
||||
|
||||
# Edit Menu
|
||||
|
||||
Edit = Editar
|
||||
Undo = Deshacer
|
||||
Redo = Rehacer
|
||||
Cant_Undo = Imposible Deshacer
|
||||
Cant_Redo = Imposible Rehacer
|
||||
Cut = Cortar
|
||||
Copy = Copiar
|
||||
Paste = Pegar
|
||||
Clear = Borrar
|
||||
Select_All = Seleccionar Todo
|
||||
Copy_To = Copiar en...
|
||||
Paste_From = Pegar Desde...
|
||||
Apply_Selection = Apply Selection
|
||||
New_Selection = New Selection
|
||||
|
||||
# Image Menu
|
||||
|
||||
Image = Imagen
|
||||
Mirror = Espejo
|
||||
Flip = Voltear
|
||||
Rotate_Right = Rotar a la Derecha
|
||||
Rotate_Left = Rotatr a la Izquierda
|
||||
Shift_Left = Desplazar a la Izquierda
|
||||
Shift_Right = Desplazar a la Derecha
|
||||
Shift_Up = Desplazar Hacia Arriba
|
||||
Shift_Down = Desplazar Hacia Abajo
|
||||
Stretch = Ajustar...
|
||||
Canvas_Size = Canvas Size...
|
||||
|
||||
# Navigate Menu
|
||||
|
||||
Navigate = Navegar
|
||||
Go_To = Ir A...
|
||||
Go_To_Again = Ir Otra Vez
|
||||
Add_To_Bookmarks = Crear Registro...
|
||||
Organize_Bookmarks = Organizar Registros...
|
||||
Bookmarks = Registros
|
||||
|
||||
# Palette menu
|
||||
|
||||
Palette = Paleta
|
||||
Edit_Colors = Editar Colores...
|
||||
Format = Formato
|
||||
Size = Tamaño...
|
||||
Import_From = Importar Desde
|
||||
This_File = Este Archivo
|
||||
Another_File = Otro Archivo...
|
||||
Add_To_Palettes = Añadir a las Paletas...
|
||||
Organize_Palettes = Organizar Paletas...
|
||||
|
||||
# Window Menu
|
||||
|
||||
Window = Ventana
|
||||
New_Window = Nueva Ventana
|
||||
Tile = Mosaico
|
||||
Cascade = Cascada
|
||||
Arrange_Icons = Orcanizar Íconos
|
||||
|
||||
# Help Menu
|
||||
|
||||
Help = Ayuda
|
||||
Help_Topics = Temas de Ayuda
|
||||
Tip_of_the_Millennium = El Consejo del Milenio...
|
||||
About_Tile_Molester = Acerca de Tile Molester
|
||||
|
||||
# Tools
|
||||
|
||||
Selection = Selección
|
||||
Dropper = Gotero
|
||||
Brush = Pincel
|
||||
Line = Línea
|
||||
Flood_Fill = Rellenar
|
||||
Color_Replacer = Reeplazar Color
|
||||
Mover = Posición
|
||||
|
||||
# Navigation button tooltips
|
||||
|
||||
Page_Back = Página Anterior
|
||||
Page_Forward = Página Siguiente
|
||||
Row_Back = Fila Anteriro
|
||||
Row_Forward = Fila Siguiente
|
||||
Tile_Back = Mosaico Anterior
|
||||
Tile_Forward = Mosaico Siguiente
|
||||
Byte_Back = Byte Anterior
|
||||
Byte_Forward = Byte Siguiente
|
||||
|
||||
# Messages
|
||||
|
||||
Save_Changes_To = Salvar cambios en
|
||||
Load_File_Error = Error cargando archivo:
|
||||
Save_File_Error = Error salvando archivo:
|
||||
Load_Bitmap_Error = Error cargando mapa de bits:
|
||||
Save_Bitmap_Error = Error salvando mapa de bits:
|
||||
Save_Settings_Error = Error salvando preferencias:
|
||||
Load_Settings_Error = Error cargando preferencias:
|
||||
Save_Resources_Error = Error salvando recursos:
|
||||
Load_Resources_Error = Error cargando recursos:
|
||||
File_Write_Error = No se puede escribir el archivo:
|
||||
Palette_Read_Error = Error leyendo los datos de la paleta:
|
||||
Parser_Config_Error = Error en la configuración del analizador de XML:
|
||||
Parser_Parse_Error = Error analizando XML:
|
||||
Parser_IO_Error = Error IO del analizador de XML:
|
||||
Out_Of_Memory = Sin memoria.
|
||||
Drugs_Message = Di no a las drogas, ¿OK?
|
||||
|
||||
# Built-in File Filter Names
|
||||
|
||||
All_Supported_Formats = Todos los Formatos Compatibles
|
||||
All_Files = All Files (*.*)
|
||||
|
||||
# Bookmark/palette organization
|
||||
|
||||
Create_In = Crear en:
|
||||
New_Folder = Directorio Nuevo...
|
||||
Rename = Cambiar Nombre
|
||||
Move = Cortar
|
||||
Delete = Borrar
|
||||
|
||||
# Confirmation button captions
|
||||
|
||||
Yes = Sí
|
||||
No = No
|
||||
OK = OK
|
||||
Cancel = Cancelar
|
||||
|
||||
# Endianness
|
||||
|
||||
Endianness = Orden de los Bytes
|
||||
Little_Endian = Intel
|
||||
Big_Endian = Motorola
|
||||
|
||||
# Dialog titles
|
||||
|
||||
New_File_Dialog_Title = Crear Nevo Archivo
|
||||
New_Folder_Dialog_Title = Crear Nevo Carpeta
|
||||
Open_File_Dialog_Title = Abrir
|
||||
Save_As_Dialog_Title = Salvar Como
|
||||
Copy_To_Dialog_Title = Copiar a
|
||||
Paste_From_Dialog_Title = Pegar Desde
|
||||
Go_To_Dialog_Title = Ir al Offset
|
||||
Stretch_Image_Dialog_Title = Ajustar Imagen
|
||||
Block_Size_Dialog_Title = Tamaño del Bloque
|
||||
Canvas_Size_Dialog_Title = Tamaño del Cuadro
|
||||
Add_To_Bookmarks_Dialog_Title = Añadir a Registros
|
||||
Organize_Bookmarks_Dialog_Title = Organizar Registros
|
||||
Add_To_Palettes_Dialog_Title = Añadir a Paletas
|
||||
Organize_Palettes_Dialog_Title = Organizar Paletas
|
||||
Edit_Colors_Dialog_Title = Ediart Colores
|
||||
Palette_Size_Dialog_Title = Cabiar Tamaño de la Paleta
|
||||
New_Palette_Dialog_Title = Crear Nueva Paleta
|
||||
Import_Internal_Palette_Dialog_Title = Importer Paleta Interna
|
||||
Open_Palette_Dialog_Title = Import Paleta Externa
|
||||
|
||||
# Incremental Canvas Size
|
||||
|
||||
Decrease_Width = Disminuir Ancho
|
||||
Increase_Width = Aunmentar Ancho
|
||||
Decrease_Height = Disminuir Alto
|
||||
Increase_Height = Aumentar Alto
|
||||
|
||||
# Input field labels
|
||||
|
||||
Folder_Name_Prompt = Nombre del Directorio:
|
||||
Description_Prompt = Descripción:
|
||||
Offset_Prompt = Offset:
|
||||
Size_Prompt = Tamaño:
|
||||
Columns_Prompt = Columnas:
|
||||
Rows_Prompt = Filas:
|
||||
|
||||
# Other
|
||||
|
||||
Empty = Vacío
|
||||
Offset = Offset
|
||||
Mode = Modo
|
||||
Radix = Radián
|
||||
Hex = Hex
|
||||
Dec = Dec
|
||||
Absolute = Absoluto
|
||||
Relative = Relativo
|
213
languages/language_template
Normal file
|
@ -0,0 +1,213 @@
|
|||
#################### Language template for Tile Molester #####################
|
||||
|
||||
# File Menu
|
||||
|
||||
File =
|
||||
New =
|
||||
Open =
|
||||
Reopen =
|
||||
Close =
|
||||
Close_All =
|
||||
Save =
|
||||
Save_As =
|
||||
Save_All =
|
||||
Exit =
|
||||
|
||||
# View Menu
|
||||
|
||||
View =
|
||||
Statusbar =
|
||||
Toolbar =
|
||||
Codec =
|
||||
Zoom =
|
||||
In =
|
||||
Out =
|
||||
1_Dimensional =
|
||||
2_Dimensional =
|
||||
Block_Size =
|
||||
Full_Canvas =
|
||||
Custom_Block_Size =
|
||||
Row_Interleave_Blocks =
|
||||
Block_Grid =
|
||||
Tile_Grid =
|
||||
Pixel_Grid =
|
||||
|
||||
# Edit Menu
|
||||
|
||||
Edit =
|
||||
Undo =
|
||||
Redo =
|
||||
Cant_Undo =
|
||||
Cant_Redo =
|
||||
Cut =
|
||||
Copy =
|
||||
Paste =
|
||||
Clear =
|
||||
Select_All =
|
||||
Copy_To =
|
||||
Paste_From =
|
||||
|
||||
# Image Menu
|
||||
|
||||
Image =
|
||||
Mirror =
|
||||
Flip =
|
||||
Rotate_Right =
|
||||
Rotate_Left =
|
||||
Shift_Left =
|
||||
Shift_Right =
|
||||
Shift_Up =
|
||||
Shift_Down =
|
||||
Stretch =
|
||||
Canvas_Size =
|
||||
|
||||
# Navigate Menu
|
||||
|
||||
Navigate =
|
||||
Go_To =
|
||||
Go_To_Again =
|
||||
Add_To_Bookmarks =
|
||||
Organize_Bookmarks =
|
||||
Bookmarks =
|
||||
|
||||
# Palette menu
|
||||
|
||||
Palette =
|
||||
Edit_Colors =
|
||||
Format =
|
||||
Size =
|
||||
Import_From =
|
||||
This_File =
|
||||
Another_File =
|
||||
Add_To_Palettes =
|
||||
Organize_Palettes =
|
||||
|
||||
# Window Menu
|
||||
|
||||
Window =
|
||||
New_Window =
|
||||
Tile =
|
||||
Cascade =
|
||||
Arrange_Icons =
|
||||
|
||||
# Help Menu
|
||||
|
||||
Help =
|
||||
Help_Topics =
|
||||
Tip_of_the_Millennium =
|
||||
About_Tile_Molester =
|
||||
|
||||
# Tools
|
||||
|
||||
Selection =
|
||||
Dropper =
|
||||
Brush =
|
||||
Line =
|
||||
Flood_Fill =
|
||||
Color_Replacer =
|
||||
Mover =
|
||||
|
||||
# Navigation button tooltips
|
||||
|
||||
Page_Back =
|
||||
Page_Forward =
|
||||
Row_Back =
|
||||
Row_Forward =
|
||||
Tile_Back =
|
||||
Tile_Forward =
|
||||
Byte_Back =
|
||||
Byte_Forward =
|
||||
|
||||
# Messages
|
||||
|
||||
Save_Changes_To =
|
||||
Load_File_Error =
|
||||
Save_File_Error =
|
||||
Load_Bitmap_Error =
|
||||
Save_Bitmap_Error =
|
||||
Save_Settings_Error =
|
||||
Load_Settings_Error =
|
||||
Save_Resources_Error =
|
||||
Load_Resources_Error =
|
||||
File_Write_Error =
|
||||
Palette_Read_Error =
|
||||
Parser_Config_Error =
|
||||
Parser_Parse_Error =
|
||||
Parser_IO_Error =
|
||||
Out_Of_Memory =
|
||||
Drugs_Message =
|
||||
|
||||
# Built-in File Filter Names
|
||||
|
||||
All_Supported_Formats =
|
||||
All_Files =
|
||||
|
||||
# Bookmark/palette organization
|
||||
|
||||
Create_In =
|
||||
New_Folder =
|
||||
Rename =
|
||||
Move =
|
||||
Delete =
|
||||
|
||||
# Confirmation button captions
|
||||
|
||||
Yes =
|
||||
No =
|
||||
OK =
|
||||
Cancel =
|
||||
|
||||
# Endianness
|
||||
|
||||
Endianness =
|
||||
Little_Endian =
|
||||
Big_Endian =
|
||||
|
||||
# Dialog titles
|
||||
|
||||
New_File_Dialog_Title =
|
||||
New_Folder_Dialog_Title =
|
||||
Open_File_Dialog_Title =
|
||||
Save_As_Dialog_Title =
|
||||
Copy_To_Dialog_Title =
|
||||
Paste_From_Dialog_Title =
|
||||
Go_To_Dialog_Title =
|
||||
Stretch_Image_Dialog_Title =
|
||||
Canvas_Size_Dialog_Title =
|
||||
Block_Size_Dialog_Title =
|
||||
Add_To_Bookmarks_Dialog_Title =
|
||||
Organize_Bookmarks_Dialog_Title =
|
||||
Add_To_Palettes_Dialog_Title =
|
||||
Organize_Palettes_Dialog_Title =
|
||||
Edit_Colors_Dialog_Title =
|
||||
Palette_Size_Dialog_Title =
|
||||
New_Palette_Dialog_Title =
|
||||
Import_Internal_Palette_Dialog_Title =
|
||||
Open_Palette_Dialog_Title =
|
||||
|
||||
# Incremental Canvas Size
|
||||
|
||||
Decrease_Width =
|
||||
Increase_Width =
|
||||
Decrease_Height =
|
||||
Increase_Height =
|
||||
|
||||
# Input field labels
|
||||
|
||||
Folder_Name_Prompt =
|
||||
Description_Prompt =
|
||||
Offset_Prompt =
|
||||
Size_Prompt =
|
||||
Columns_Prompt =
|
||||
Rows_Prompt =
|
||||
|
||||
# Other
|
||||
|
||||
Empty =
|
||||
Offset =
|
||||
Mode =
|
||||
Radix =
|
||||
Hex =
|
||||
Dec =
|
||||
Absolute =
|
||||
Relative =
|
38
resources/tmres.dtd
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
|
||||
<!ELEMENT tmres (bookmarks?, palettes?)>
|
||||
|
||||
<!ELEMENT bookmarks (folder | bookmark)*>
|
||||
|
||||
<!ELEMENT palettes (folder | palette)*>
|
||||
|
||||
<!ELEMENT folder (name, (folder | bookmark | palette)*)>
|
||||
|
||||
<!ELEMENT bookmark (description)>
|
||||
<!ATTLIST bookmark
|
||||
offset CDATA #REQUIRED
|
||||
columns CDATA #REQUIRED
|
||||
rows CDATA #REQUIRED
|
||||
blockwidth CDATA #REQUIRED
|
||||
blockheight CDATA #REQUIRED
|
||||
rowinterleaved (true | false) #REQUIRED
|
||||
sizeblocktocanvas (true | false) #REQUIRED
|
||||
mode (1D | 2D) #REQUIRED
|
||||
codec CDATA #REQUIRED
|
||||
palIndex CDATA #REQUIRED
|
||||
palette CDATA #IMPLIED
|
||||
>
|
||||
|
||||
<!ELEMENT palette (description, data?)>
|
||||
<!ATTLIST palette
|
||||
id ID #IMPLIED
|
||||
direct (yes | no) #REQUIRED
|
||||
codec CDATA #REQUIRED
|
||||
size CDATA #REQUIRED
|
||||
offset CDATA #IMPLIED
|
||||
endianness (little | big) "little"
|
||||
>
|
||||
|
||||
<!ELEMENT data (#PCDATA)>
|
||||
<!ELEMENT name (#PCDATA)>
|
||||
<!ELEMENT description (#PCDATA)>
|
9
settings.dtd
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
|
||||
<!ELEMENT settings (property*)>
|
||||
|
||||
<!ELEMENT property EMPTY>
|
||||
<!ATTLIST property
|
||||
key CDATA #REQUIRED
|
||||
value CDATA #REQUIRED
|
||||
>
|
9
settings.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE settings SYSTEM "settings.dtd">
|
||||
<settings>
|
||||
<property key="locale" value="en_US"/>
|
||||
<property key="viewStatusBar" value="true"/>
|
||||
<property key="viewToolBar" value="true"/>
|
||||
<property key="maxRecentFiles" value="10"/>
|
||||
<property key="lastPath" value=""/>
|
||||
</settings>
|
52
src/TileMolester.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import tm.ui.TMUI;
|
||||
|
||||
/**
|
||||
*
|
||||
* Tile Molester main class.
|
||||
* A quite pointless class really. The application is very UI-centric,
|
||||
* so the TMUI class evolved into the real application backbone.
|
||||
* This class just gets the show started.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TileMolester {
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
**/
|
||||
|
||||
public TileMolester() {
|
||||
new TMUI();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Starts up the program.
|
||||
*
|
||||
**/
|
||||
|
||||
public static void main(String[] args) {
|
||||
new TileMolester();
|
||||
}
|
||||
|
||||
}
|
221
src/tm/FileImage.java
Normal file
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm;
|
||||
|
||||
import tm.treenodes.*;
|
||||
import tm.ui.TMView;
|
||||
import java.io.File;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
*
|
||||
* A FileImage object represents a file that has been loaded into the editor.
|
||||
*
|
||||
**/
|
||||
|
||||
public class FileImage {
|
||||
|
||||
private byte[] contents;
|
||||
private File file;
|
||||
private Vector views = new Vector();
|
||||
private static int fileNum=0;
|
||||
private boolean modified;
|
||||
private TMFileResources resources;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a FileImage from a file on disk.
|
||||
*
|
||||
* The contents of the file have already been read into buffer contents.
|
||||
*
|
||||
**/
|
||||
|
||||
public FileImage(File file, byte[] contents) {
|
||||
this.file = file;
|
||||
this.contents = contents;
|
||||
this.resources = null;
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a blank FileImage of the requested size.
|
||||
*
|
||||
**/
|
||||
|
||||
public FileImage(int size) throws OutOfMemoryError {
|
||||
file = new File(System.getProperty("user.dir") + (fileNum++));
|
||||
this.resources = null;
|
||||
try {
|
||||
contents = new byte[size];
|
||||
}
|
||||
catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
}
|
||||
// fill with zeroes
|
||||
for (int i=0; i<size; i++) {
|
||||
contents[i] = 0x00;
|
||||
}
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the file.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setFile(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the file.
|
||||
*
|
||||
**/
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the binary contents of this FileImage.
|
||||
*
|
||||
**/
|
||||
|
||||
public byte[] getContents() {
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the size (in # of bytes) of the file contents.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getSize() {
|
||||
return contents.length;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the name of the file.
|
||||
*
|
||||
**/
|
||||
|
||||
public String getName() {
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the TMViews associated with this FileImage.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMView[] getViews() {
|
||||
TMView[] vs = new TMView[views.size()];
|
||||
for (int i=0; i<vs.length; i++) {
|
||||
vs[i] = (TMView)views.get(i);
|
||||
}
|
||||
return vs;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Adds a TMView to this FileImage.
|
||||
*
|
||||
**/
|
||||
|
||||
public void addView(TMView view) {
|
||||
views.add(view);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Removes a TMView for this FileImage.
|
||||
*
|
||||
**/
|
||||
|
||||
public void removeView(TMView view) {
|
||||
views.remove(view);
|
||||
if (views.size() == 0) {
|
||||
contents = null; // kill the contents reference
|
||||
resources = null;
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the modified flag.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setModified(boolean modified) {
|
||||
this.modified = modified;
|
||||
// update view titles
|
||||
TMView[] views = getViews();
|
||||
for (int i=0; i<views.length; i++) {
|
||||
TMView v = views[i];
|
||||
String t = v.getTitle();
|
||||
if (modified) {
|
||||
v.setTitle(getName()+"*");
|
||||
v.getTMUI().setSaveButtonsEnabled(true);
|
||||
}
|
||||
else {
|
||||
v.setTitle(getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the modified flag.
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean isModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the resources associated with this fileimage.
|
||||
* The resources contain bookmarks and palettes.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setResources(TMFileResources resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the resources related to this fileimage.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMFileResources getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
}
|
135
src/tm/TMBitmapExporter.java
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm;
|
||||
|
||||
import tm.gfxlibs.*;
|
||||
import tm.canvases.TMTileCanvas;
|
||||
import tm.osbaldeston.image.BMP;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.stream.FileImageOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
/**
|
||||
*
|
||||
* Allows a tile canvas to be saved to a bitmap.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMBitmapExporter {
|
||||
|
||||
/**
|
||||
*
|
||||
* Static method that takes a tile canvas and a file and saves the tiles as a
|
||||
* bitmap. The bitmap format is determined by the extension of the file.
|
||||
*
|
||||
**/
|
||||
|
||||
public static void saveTileCanvasToFile(TMTileCanvas canvas, File file)
|
||||
throws Exception {
|
||||
Image img = canvas.getImage();
|
||||
String ext = getExtension(file);
|
||||
// use proper encoder based on file extension
|
||||
if (ext.equals("bmp")) {
|
||||
BMP bmp = new BMP(img);
|
||||
bmp.write(file);
|
||||
}
|
||||
else if (ext.equals("gif")) {
|
||||
try {
|
||||
FileOutputStream fis = new FileOutputStream(file);
|
||||
GIFOutputStream.writeGIF(fis, img, GIFOutputStream.STANDARD_256_COLORS);
|
||||
fis.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else if (ext.equals("jpg")) {
|
||||
ImageWriter jpegEncoder = (ImageWriter)ImageIO.getImageWritersByFormatName("jpeg").next();
|
||||
if (jpegEncoder != null) {
|
||||
try {
|
||||
FileImageOutputStream fios = new FileImageOutputStream(file);
|
||||
jpegEncoder.setOutput(fios);
|
||||
jpegEncoder.write(convertToRenderedImage(img));
|
||||
fios.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ext.equals("png")) {
|
||||
try {
|
||||
byte[] pngBytes = new PngEncoder(img).pngEncode();
|
||||
FileOutputStream fis = new FileOutputStream(file);
|
||||
fis.write(pngBytes);
|
||||
fis.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else if (ext.equals("pcx")) {
|
||||
try {
|
||||
byte[] pcxBytes = PCXEncoder.encode(img);
|
||||
FileOutputStream fis = new FileOutputStream(file);
|
||||
fis.write(pcxBytes);
|
||||
fis.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets file extension.
|
||||
*
|
||||
**/
|
||||
|
||||
public static String getExtension(File f) {
|
||||
String ext = "";
|
||||
String s = f.getName();
|
||||
int i = s.lastIndexOf('.');
|
||||
|
||||
if (i > 0 && i < s.length() - 1) {
|
||||
ext = s.substring(i+1).toLowerCase();
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts an Image to a RenderedImage so that it can be fed to the JPEG encoder.
|
||||
*
|
||||
**/
|
||||
|
||||
public static RenderedImage convertToRenderedImage(Image img) {
|
||||
BufferedImage bi = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = bi.createGraphics();
|
||||
g2d.drawImage(img, 0,0,img.getWidth(null), img.getHeight(null), null);
|
||||
return bi;
|
||||
}
|
||||
|
||||
}
|
130
src/tm/TMBitmapImporter.java
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm;
|
||||
|
||||
import tm.tilecodecs.*;
|
||||
import tm.canvases.TMTileCanvas;
|
||||
import tm.osbaldeston.image.BMP;
|
||||
import tm.gfxlibs.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* Allows a bitmap to be loaded and converted to a tile canvas.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMBitmapImporter {
|
||||
|
||||
/**
|
||||
*
|
||||
* Static method that takes a file, tries to load the file as a bitmap and
|
||||
* convert it to a tile canvas. The file extension is used to determine what
|
||||
* bitmap format to use when loading the file.
|
||||
*
|
||||
**/
|
||||
|
||||
public static TMTileCanvas loadTileCanvasFromFile(File file)
|
||||
throws Exception {
|
||||
Image img = null;
|
||||
String ext = getExtension(file);
|
||||
// use proper decoder based on file extension
|
||||
if (ext.equals("bmp")) {
|
||||
// use BMP decoder
|
||||
BMP bmp = new BMP(file);
|
||||
img = bmp.getImage();
|
||||
}
|
||||
else if (ext.equals("pcx")) {
|
||||
// use PCX decoder
|
||||
try {
|
||||
img = PCXReader.loadImage(new FileInputStream(file));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// use Java's decoders
|
||||
ImageIcon icon = new ImageIcon(file.getAbsolutePath());
|
||||
img = icon.getImage();
|
||||
}
|
||||
if (img == null) {
|
||||
throw new Exception(); // couldn't load the image
|
||||
}
|
||||
|
||||
int w = img.getWidth(null);
|
||||
int h = img.getHeight(null);
|
||||
int[] pixels = new int[w * h];
|
||||
|
||||
// grab the pixels
|
||||
PixelGrabber pg = new PixelGrabber(img, 0, 0, w, h, pixels, 0, w);
|
||||
try {
|
||||
pg.grabPixels();
|
||||
} catch (InterruptedException e) {
|
||||
throw e;
|
||||
}
|
||||
if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
DirectColorTileCodec codec = new DirectColorTileCodec("", 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, "");
|
||||
// copy to a new canvas
|
||||
int cols = w / 8;
|
||||
int rows = h / 8;
|
||||
byte[] bits = new byte[cols * rows * codec.getTileSize()];
|
||||
TMTileCanvas tc = new TMTileCanvas(bits);
|
||||
tc.setGridSize(cols, rows);
|
||||
tc.setPalette(null);
|
||||
tc.setMode(TileCodec.MODE_1D);
|
||||
tc.setCodec(codec);
|
||||
int w2 = tc.getCanvasWidth();
|
||||
int h2 = tc.getCanvasHeight();
|
||||
for (int i=0; i<h2; i++) {
|
||||
for (int j=0; j<w2; j++) {
|
||||
tc.setPixel(j, i, pixels[(i * w) + j]);
|
||||
}
|
||||
}
|
||||
|
||||
tc.packPixels();
|
||||
|
||||
return tc;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets file extension.
|
||||
*
|
||||
**/
|
||||
|
||||
public static String getExtension(File f) {
|
||||
String ext = "";
|
||||
String s = f.getName();
|
||||
int i = s.lastIndexOf('.');
|
||||
|
||||
if (i > 0 && i < s.length() - 1) {
|
||||
ext = s.substring(i+1).toLowerCase();
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
}
|
358
src/tm/TMFileResources.java
Normal file
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm;
|
||||
|
||||
import tm.colorcodecs.*;
|
||||
import tm.tilecodecs.*;
|
||||
import tm.treenodes.*;
|
||||
import tm.utils.*;
|
||||
import tm.ui.TMUI;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.w3c.dom.Node;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
*
|
||||
* Holds resources for a file image.
|
||||
* Resources include bookmarks and palettes.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMFileResources {
|
||||
|
||||
private FolderNode bookmarkRoot;
|
||||
private FolderNode paletteRoot;
|
||||
|
||||
private FileImage fileImage;
|
||||
private TMUI ui;
|
||||
|
||||
/**
|
||||
*
|
||||
* Create initial resources for the specified fileimage.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMFileResources(FileImage fileImage, TMUI ui) {
|
||||
this.bookmarkRoot = new FolderNode(ui.xlate("Bookmarks"));
|
||||
this.paletteRoot = new FolderNode(ui.xlate("Palettes"));
|
||||
fileImage.setResources(this);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Loads resources for a fileimage from an XML document.
|
||||
*
|
||||
* Resources are in an XML tree structure like so:
|
||||
* +resources
|
||||
* +resourcetype1
|
||||
* +resource1
|
||||
* +resource2
|
||||
* +resourcetype2
|
||||
* ....
|
||||
* +resourcetype3
|
||||
*
|
||||
**/
|
||||
|
||||
public TMFileResources(File file, FileImage fileImage, TMUI ui)
|
||||
throws SAXException, ParserConfigurationException, IOException {
|
||||
Document doc = null;
|
||||
try {
|
||||
doc = XMLParser.parse(file);
|
||||
} catch (SAXException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (doc == null) return;
|
||||
this.fileImage = fileImage;
|
||||
this.ui = ui;
|
||||
|
||||
Element root = doc.getDocumentElement();
|
||||
bookmarkRoot = parseBookmarks(root);
|
||||
paletteRoot = parsePalettes(root);
|
||||
|
||||
fileImage.setResources(this);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Parses the bookmarks into a tree of TMTreeNodes.
|
||||
* Bookmarks are in an XML tree structure like so:
|
||||
* +folder1
|
||||
* +bookmark1
|
||||
* +bookmark2
|
||||
* +folder2
|
||||
* +folder3
|
||||
* +bookmark1
|
||||
* ...
|
||||
*
|
||||
**/
|
||||
|
||||
public FolderNode parseBookmarks(Element root) {
|
||||
Element e = getChildTag(root, "bookmarks", 0);
|
||||
FolderNode bookmarkRoot = new FolderNode(ui.xlate("Bookmarks"));
|
||||
if (e != null) {
|
||||
// parse bookmarks into tree
|
||||
NodeList children = e.getChildNodes();
|
||||
for (int i=0; i<children.getLength(); i++) {
|
||||
if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
|
||||
addToBookmarksTree((Element)children.item(i), bookmarkRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bookmarkRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Recursively adds folders and bookmarks to tree.
|
||||
*
|
||||
**/
|
||||
|
||||
public void addToBookmarksTree(Element e, FolderNode folder) {
|
||||
if (e.getTagName().equals("folder")) {
|
||||
// subfolder
|
||||
String name = XMLParser.getNodeValue(getChildTag(e, "name", 0));
|
||||
FolderNode subFolder = new FolderNode(name);
|
||||
folder.add(subFolder);
|
||||
NodeList children = e.getChildNodes();
|
||||
for (int i=0; i<children.getLength(); i++) {
|
||||
if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
|
||||
addToBookmarksTree((Element)children.item(i), subFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (e.getTagName().equals("bookmark")) {
|
||||
// read a bookmark
|
||||
int offset = Integer.parseInt(e.getAttribute("offset"));
|
||||
int cols = Integer.parseInt(e.getAttribute("columns"));
|
||||
int rows = Integer.parseInt(e.getAttribute("rows"));
|
||||
int blockWidth = Integer.parseInt(e.getAttribute("blockwidth"));
|
||||
int blockHeight = Integer.parseInt(e.getAttribute("blockheight"));
|
||||
boolean rowInterleaved = e.getAttribute("rowinterleaved").equals("true");
|
||||
boolean sizeBlockToCanvas = e.getAttribute("sizeblocktocanvas").equals("true");
|
||||
int mode = TileCodec.MODE_1D;
|
||||
if (e.getAttribute("mode").equals("2D")) {
|
||||
mode = TileCodec.MODE_2D;
|
||||
}
|
||||
int palIndex = Integer.parseInt(e.getAttribute("palIndex"));
|
||||
// String palID = e.getAttribute("palette");
|
||||
String codecID = e.getAttribute("codec");
|
||||
TileCodec codec = ui.getTileCodecByID(codecID);
|
||||
String desc = XMLParser.getNodeValue(getChildTag(e, "description", 0));
|
||||
BookmarkItemNode bookmark =
|
||||
new BookmarkItemNode(
|
||||
offset,
|
||||
cols,
|
||||
rows,
|
||||
blockWidth,
|
||||
blockHeight,
|
||||
rowInterleaved,
|
||||
sizeBlockToCanvas,
|
||||
mode,
|
||||
palIndex,
|
||||
codec,
|
||||
desc
|
||||
);
|
||||
folder.add(bookmark);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Parses the palettes into a tree of TMTreeNodes.
|
||||
* Highly analoguous to bookmark parsing.
|
||||
*
|
||||
**/
|
||||
|
||||
public FolderNode parsePalettes(Element root) {
|
||||
Element e = getChildTag(root, "palettes", 0);
|
||||
FolderNode paletteRoot = new FolderNode(ui.xlate("Palettes"));
|
||||
if (e != null) {
|
||||
// parse palettes into tree
|
||||
NodeList children = e.getChildNodes();
|
||||
for (int i=0; i<children.getLength(); i++) {
|
||||
if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
|
||||
addToPalettesTree((Element)children.item(i), paletteRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paletteRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Recursively processes and adds folders and palettes to tree.
|
||||
*
|
||||
**/
|
||||
|
||||
public void addToPalettesTree(Element e, FolderNode folder) {
|
||||
if (e.getTagName().equals("folder")) {
|
||||
// subfolder
|
||||
String name = XMLParser.getNodeValue(getChildTag(e, "name", 0));
|
||||
FolderNode subFolder = new FolderNode(name);
|
||||
folder.add(subFolder);
|
||||
NodeList children = e.getChildNodes();
|
||||
for (int i=0; i<children.getLength(); i++) {
|
||||
if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
|
||||
addToPalettesTree((Element)children.item(i), subFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (e.getTagName().equals("palette")) {
|
||||
// read palette
|
||||
String id = e.getAttribute("id");
|
||||
boolean direct = e.getAttribute("direct").equals("yes");
|
||||
String codecID = e.getAttribute("codec");
|
||||
ColorCodec codec = ui.getColorCodecByID(codecID);
|
||||
int size = Integer.parseInt(e.getAttribute("size"));
|
||||
int offset = 0;
|
||||
int endianness = ColorCodec.LITTLE_ENDIAN;
|
||||
if (e.getAttribute("endianness").equals("big")) {
|
||||
endianness = ColorCodec.BIG_ENDIAN;
|
||||
}
|
||||
byte[] data;
|
||||
if (direct) {
|
||||
// data is in XML, parse it
|
||||
String hexString = XMLParser.getNodeValue(getChildTag(e, "data", 0));
|
||||
// alt: hexString = ((CDATASection)palette.getFirstChild()).getData());
|
||||
data = HexStringConverter.hexStringToBytes(hexString);
|
||||
}
|
||||
else {
|
||||
// data is in file
|
||||
offset = Integer.parseInt(e.getAttribute("offset"));
|
||||
data = fileImage.getContents();
|
||||
}
|
||||
String desc = XMLParser.getNodeValue(getChildTag(e, "description", 0));
|
||||
TMPalette pal = new TMPalette(id, data, offset, size, codec, endianness, direct, false);
|
||||
PaletteItemNode palette =
|
||||
new PaletteItemNode(
|
||||
pal,
|
||||
desc
|
||||
);
|
||||
folder.add(palette);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the root of the tree of bookmarks & bookmark folders.
|
||||
*
|
||||
**/
|
||||
|
||||
public FolderNode getBookmarksRoot() {
|
||||
return bookmarkRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the root of the tree of palettes & palette folders.
|
||||
*
|
||||
**/
|
||||
|
||||
public FolderNode getPalettesRoot() {
|
||||
return paletteRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns XML representation of resources.
|
||||
*
|
||||
**/
|
||||
|
||||
public String toXML() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n");
|
||||
sb.append("<!DOCTYPE tmres SYSTEM \"tmres.dtd\">\n");
|
||||
sb.append("<tmres>\n");
|
||||
|
||||
sb.append(bookmarksToXML());
|
||||
sb.append(palettesToXML());
|
||||
|
||||
sb.append("</tmres>\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Return XML representation of bookmarks.
|
||||
*
|
||||
**/
|
||||
|
||||
public String bookmarksToXML() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(" <bookmarks>\n");
|
||||
TMTreeNode[] children = bookmarkRoot.getChildren();
|
||||
for (int i=0; i<children.length; i++) {
|
||||
sb.append(children[i].toXML());
|
||||
}
|
||||
sb.append(" </bookmarks>\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns XML representation of palettes.
|
||||
*
|
||||
**/
|
||||
|
||||
public String palettesToXML() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(" <palettes>\n");
|
||||
TMTreeNode[] children = paletteRoot.getChildren();
|
||||
for (int i=0; i<children.length; i++) {
|
||||
sb.append(children[i].toXML());
|
||||
}
|
||||
sb.append(" </palettes>\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the default resource filename for the given file.
|
||||
* Currently, this is [filename-extension+".xml"]
|
||||
*
|
||||
**/
|
||||
|
||||
public static File getResourceFileFor(File file) {
|
||||
// determine name of XML resource file based on filename
|
||||
String name = file.getName();
|
||||
int i = name.lastIndexOf('.');
|
||||
if (i != -1) {
|
||||
name = name.substring(0, i);
|
||||
}
|
||||
name += ".xml";
|
||||
return new File("./resources/"+name);
|
||||
}
|
||||
|
||||
private Element getChildTag(Element e, String Tag, int i) {
|
||||
return (Element)e.getElementsByTagName(Tag).item(i);
|
||||
}
|
||||
|
||||
}
|
457
src/tm/TMPalette.java
Normal file
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm;
|
||||
|
||||
import tm.colorcodecs.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* Custom palette class.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMPalette {
|
||||
|
||||
// the default 256-color palette
|
||||
public static final int[] defaultPalette = {
|
||||
0x000000, 0xA04020, 0xD0A050, 0xF0F080, 0x860033, 0x0073BF, 0xFFCF00, 0xB4EBEF,
|
||||
0x000093, 0x00FF51, 0x00ACFF, 0xA411BC, 0x000000, 0xF28C59, 0x9F00B6, 0x00DC83,
|
||||
0x090021, 0xFFFFFF, 0xFF9F00, 0xFF0003, 0xFFAFC2, 0xF0004B, 0xE136FC, 0x6D0473,
|
||||
0x000300, 0xFFF94B, 0x9FA300, 0x636000, 0x000000, 0xF05CF9, 0xEF6B84, 0x00FF5D,
|
||||
0x000000, 0x99E200, 0xFF2F4C, 0x5EFFD6, 0x002D24, 0xFFFF00, 0x4AC234, 0x4838F5,
|
||||
0x4300D6, 0xFF0000, 0xE8890B, 0x00FC84, 0x004066, 0xFFD200, 0xBBD96B, 0xFF0067,
|
||||
0xFF6900, 0x330059, 0xFFFF4B, 0x1BACFB, 0x5EAAE3, 0x4F5DF9, 0xB12A8E, 0x6DF674,
|
||||
0xEB07E3, 0x9EB76E, 0xEE5FD6, 0x2BF8BA, 0x723338, 0x5D063C, 0xDC9C6F, 0xAA2B12,
|
||||
0x176599, 0xC6671B, 0x61EC0D, 0x820194, 0x978F12, 0x77EAB5, 0x4A9936, 0x099FAF,
|
||||
0x956558, 0x0FD972, 0xF24CA5, 0x47F7A2, 0x067996, 0xA1ABB4, 0x633056, 0xE02D81,
|
||||
0x2A5836, 0x9DCAE0, 0x65C043, 0xC4B9D5, 0x11A983, 0xF6DB7D, 0x8742B1, 0xB17615,
|
||||
0x218E7A, 0xF1577E, 0x943F95, 0xEADDED, 0xC0D74E, 0xA84090, 0xA79252, 0xF8EE81,
|
||||
0xBCCD29, 0x0EA376, 0xE5A101, 0x284457, 0xB5512D, 0x2FC653, 0xEBA13F, 0x2DFDA6,
|
||||
0x479061, 0x41BBDC, 0xC53597, 0x5F4CBE, 0xBF6749, 0x78AE54, 0xB5BD0C, 0x7641F9,
|
||||
0x69B4F7, 0xD7049F, 0x7A487F, 0x52AD20, 0x6E7F52, 0x5A9633, 0x607DDB, 0x02355C,
|
||||
0xF07278, 0xE8DE01, 0xA19835, 0xA0B90E, 0x98EE91, 0xBC46BD, 0xBD67CE, 0xE87CA2,
|
||||
0x7B6CFB, 0x0ACF97, 0xD3682E, 0x5CFB6A, 0x243CA9, 0xE943F2, 0x1D736F, 0x26F742,
|
||||
0x7A8E1A, 0xE708FD, 0x4006EB, 0xA2AEF0, 0xF085CB, 0x22DDC2, 0xEB03CB, 0x879218,
|
||||
0x5E0F42, 0x3E2F83, 0x73880A, 0x56707B, 0x9526A4, 0xA23E37, 0x2959BE, 0xB115E2,
|
||||
0x29F61D, 0xCA4BC2, 0x234876, 0x2DC7E5, 0xC735C2, 0xF41484, 0x6B7D1D, 0x1C4FAD,
|
||||
0x79AAC3, 0xA90949, 0xF4CD7B, 0x7BD0AE, 0x25EA81, 0x2B5E23, 0xB50961, 0x53DC47,
|
||||
0xF58C41, 0x41DCE8, 0x423AA4, 0xDE3864, 0x2A45A6, 0x011F57, 0x75A7B4, 0x621B26,
|
||||
0xDE88C0, 0xC05A68, 0xE9AE99, 0x81E7B6, 0x514F2C, 0x9E72A6, 0x5EF1C2, 0xF7175A,
|
||||
0xFD219A, 0x8EF152, 0x2171FC, 0x502258, 0x88C63A, 0x1945F8, 0x3CAA21, 0xD7A067,
|
||||
0x52CCF2, 0x4CE400, 0x43CC98, 0x1EDCF4, 0x076F0E, 0xB79188, 0xAF0F2B, 0x0AD644,
|
||||
0xD85343, 0xB6ECA3, 0x0662BB, 0x5A816A, 0x26A60E, 0xC21E72, 0xDFC212, 0x286621,
|
||||
0xF6FAD6, 0x2BA27D, 0x5F6F12, 0x306F18, 0xF80C80, 0x1066A7, 0xAC784C, 0x9275FA,
|
||||
0x28C6D3, 0x2C85D8, 0x8EF002, 0xE12571, 0x7A8AFB, 0x2B6B5E, 0x73862A, 0x44E76F,
|
||||
0x9DCDF8, 0x375493, 0xEA65D1, 0xD745F0, 0x3352E7, 0xD648A3, 0xDC30AD, 0x920FD5,
|
||||
0xE9572A, 0x35A93C, 0x9BFCA9, 0x37F35D, 0x143E89, 0x7FA505, 0xD85EFA, 0x63DF30,
|
||||
0xC8CCB8, 0xDD0EA2, 0x1F28D4, 0xDF6DB3, 0xEDD7A7, 0x909954, 0x88BE41, 0x9DBCA7,
|
||||
0x1B3801, 0x91A0A0, 0xCFD4F6, 0x76CDD7, 0x6D318A, 0x22D03B, 0x7750D3, 0x553080
|
||||
};
|
||||
|
||||
private static int palNum=0;
|
||||
|
||||
private String id; // unique ID for this palette
|
||||
private int[] entries; // stored in native format
|
||||
private int[] rgbvalues; // corresponding RGB values
|
||||
|
||||
private ColorCodec codec; // used to convert from/to native/RGB format
|
||||
private int endianness;
|
||||
private boolean direct; // Mewster: I SUPPOSE it means a palette has been loaded from another file
|
||||
private int offset;
|
||||
|
||||
private boolean modified;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a new palette with given format and size.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMPalette(String id, int size, ColorCodec codec, int endianness) {
|
||||
this.id = id;
|
||||
this.codec = codec;
|
||||
this.endianness = endianness;
|
||||
this.direct = true;
|
||||
this.offset = 0;
|
||||
entries = new int[size];
|
||||
rgbvalues = new int[size];
|
||||
// set all entries to zero
|
||||
for (int i=0; i<size; i++) {
|
||||
setEntry(i, 0);
|
||||
}
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a new palette with given format and entries.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMPalette(String id, int[] entries, ColorCodec codec, int endianness, boolean direct) {
|
||||
this.id = id;
|
||||
this.codec = codec;
|
||||
this.endianness = endianness;
|
||||
this.direct = direct;
|
||||
this.offset = 0;
|
||||
this.entries = new int[entries.length];
|
||||
rgbvalues = new int[entries.length];
|
||||
for (int i=0; i<entries.length; i++) {
|
||||
setEntry(i, entries[i]);
|
||||
}
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a new palette with given format and entries.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMPalette(String id, byte[] bytes, int offset, int size, ColorCodec codec, int endianness, boolean direct, boolean modified) {
|
||||
this.id = id;
|
||||
this.codec = codec;
|
||||
this.endianness = endianness;
|
||||
this.offset = offset;
|
||||
this.direct = direct;
|
||||
this.entries = new int[size];
|
||||
rgbvalues = new int[size];
|
||||
codec.setEndianness(endianness);
|
||||
for (int i=0; i<size; i++) {
|
||||
setEntry(i, codec.fromBytes(bytes, offset + (i * codec.getBytesPerPixel())));
|
||||
}
|
||||
setModified(modified);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a new palette from an existing palette.
|
||||
* The settings and entries are copied from the given palette.
|
||||
* TODO: How to create unique ID??
|
||||
*
|
||||
**/
|
||||
|
||||
public TMPalette(TMPalette palette) {
|
||||
id = "PAL" + (palNum++); // TODO
|
||||
codec = palette.getCodec();
|
||||
endianness = palette.getEndianness();
|
||||
offset = palette.getOffset();
|
||||
direct = palette.isDirect();
|
||||
entries = new int[palette.getSize()];
|
||||
rgbvalues = new int[palette.getSize()];
|
||||
for (int i=0; i<palette.getSize(); i++) {
|
||||
entries[i] = palette.getEntry(i);
|
||||
rgbvalues[i] = palette.getEntryRGB(i);
|
||||
}
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the file offset where this palette was loaded from (0 if it wasn't
|
||||
* loaded from a file.)
|
||||
*
|
||||
**/
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns whether this palette is "direct".
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean isDirect() {
|
||||
return direct;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the endianness.
|
||||
* This is needed when converting the array of entries from/to an
|
||||
* array of bytes.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setEndianness(int endianness) {
|
||||
this.endianness = endianness;
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the endianness.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getEndianness() {
|
||||
return endianness;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the ID for this palette.
|
||||
*
|
||||
**/
|
||||
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets a palette entry.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setEntry(int index, int value) {
|
||||
entries[index] = value;
|
||||
rgbvalues[index] = codec.decode(value);
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets a palette entry by RGB value.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setEntryRGB(int index, int rgb) {
|
||||
entries[index] = codec.encode(rgb);
|
||||
rgbvalues[index] = codec.decode(entries[index]);
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets a palette entry.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getEntry(int index) {
|
||||
if (index >= entries.length) {
|
||||
System.out.println("Entry doesn't exist");
|
||||
return 0;
|
||||
}
|
||||
return entries[index];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets a palette entry's 24-bit RGB value.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getEntryRGB(int index) throws ArrayIndexOutOfBoundsException {
|
||||
int val = 0;
|
||||
try {
|
||||
val = rgbvalues[index];
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
val = 0;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the palette entry as a byte array.
|
||||
*
|
||||
**/
|
||||
|
||||
public byte[] getEntryBytes(int index) throws ArrayIndexOutOfBoundsException {
|
||||
byte[] bytes = new byte[codec.getBytesPerPixel()];
|
||||
codec.setEndianness(endianness);
|
||||
try {
|
||||
codec.toBytes(entries[index], bytes, 0);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
return bytes;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets index of rgb value _relative_ to startIndex.
|
||||
*
|
||||
**/
|
||||
|
||||
public int indexOf(int startIndex, int rgbval) {
|
||||
for (int i=0; i<entries.length-startIndex; i++) {
|
||||
if (getEntryRGB(startIndex+i) == rgbval) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
System.out.println("RGB value not in palette");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the array of palette entries.
|
||||
*
|
||||
**/
|
||||
|
||||
public int[] getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the size of the palette.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getSize() {
|
||||
return entries.length;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Resizes the palette.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setSize(int size) {
|
||||
int[] tempEntries = new int[size];
|
||||
int[] tempRgbvalues = new int[size];
|
||||
if (size > entries.length) {
|
||||
// copy all old entries
|
||||
for (int i=0; i<entries.length; i++) {
|
||||
tempEntries[i] = entries[i];
|
||||
tempRgbvalues[i] = rgbvalues[i];
|
||||
}
|
||||
// clear remaining entries
|
||||
for (int i=entries.length; i<size; i++) {
|
||||
tempEntries[i] = codec.encode(0x000000);
|
||||
tempRgbvalues[i] = 0x000000;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// copy as many old entries as possible
|
||||
for (int i=0; i<size; i++) {
|
||||
tempEntries[i] = entries[i];
|
||||
tempRgbvalues[i] = rgbvalues[i];
|
||||
}
|
||||
}
|
||||
entries = tempEntries;
|
||||
rgbvalues = tempRgbvalues;
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the color codec. Converts entries to the new format.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setCodec(ColorCodec codec) {
|
||||
this.codec = codec;
|
||||
// do conversion
|
||||
for (int i=0; i<entries.length; i++) {
|
||||
setEntry(i, codec.encode(rgbvalues[i]));
|
||||
}
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the color codec for this palette.
|
||||
*
|
||||
**/
|
||||
|
||||
public ColorCodec getCodec() {
|
||||
return codec;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns the palette entry # whose RGB components match the given value the closest.
|
||||
* startIndex..colorCount is the range of colors that are tried.
|
||||
* PS: The returned index is RELATIVE to startIndex.
|
||||
*
|
||||
**/
|
||||
|
||||
public int closestMatchingEntry(int startIndex, int colorCount, int argb) {
|
||||
int targetR = (argb & 0x00FF0000) >> 16;
|
||||
int targetG = (argb & 0x0000FF00) >> 8;
|
||||
int targetB = (argb & 0x000000FF);
|
||||
int bestEntry=0, bestDiff=1000000;
|
||||
for (int i=0; i<colorCount; i++) {
|
||||
int val = getEntryRGB(startIndex + i);
|
||||
int r = (val & 0x00FF0000) >> 16;
|
||||
int g = (val & 0x0000FF00) >> 8;
|
||||
int b = (val & 0x000000FF);
|
||||
int diff = Math.abs(targetR - r) + Math.abs(targetG - g) + Math.abs(targetB - b);
|
||||
if (diff < bestDiff) {
|
||||
bestDiff = diff;
|
||||
bestEntry = i;
|
||||
}
|
||||
}
|
||||
return bestEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Convenience method for getting the actual RGB value of the closest matching
|
||||
* entry, instead of the palette index.
|
||||
*
|
||||
**/
|
||||
|
||||
public int closestMatchingEntryRGB(int startIndex, int colorCount, int argb) {
|
||||
return getEntryRGB(startIndex + closestMatchingEntry(startIndex, colorCount, argb));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns the palette entries as a byte array.
|
||||
*
|
||||
**/
|
||||
|
||||
public byte[] entriesToBytes() {
|
||||
byte[] bytes = new byte[entries.length * codec.getBytesPerPixel()];
|
||||
codec.setEndianness(endianness);
|
||||
for (int i=0; i<entries.length; i++) {
|
||||
codec.toBytes(entries[i], bytes, i * codec.getBytesPerPixel());
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the modified state of this palette.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setModified(boolean modified) {
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the modified state of this palette.
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean isModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
}
|
371
src/tm/TMSpecReader.java
Normal file
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm;
|
||||
|
||||
import tm.colorcodecs.*;
|
||||
import tm.tilecodecs.*;
|
||||
import tm.fileselection.*;
|
||||
import tm.utils.*;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Vector;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.w3c.dom.Node;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
*
|
||||
* This class has methods for reading an XML document containing specifications
|
||||
* for the various program resources.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMSpecReader {
|
||||
|
||||
private static Vector colorcodecs;
|
||||
private static Vector tilecodecs;
|
||||
private static Vector filefilters;
|
||||
private static Vector palettefilters;
|
||||
private static Vector filelisteners;
|
||||
private static Element tmspec;
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads specs from the specified file.
|
||||
*
|
||||
**/
|
||||
|
||||
public static void readSpecsFromFile(File file)
|
||||
throws SAXException, ParserConfigurationException, IOException {
|
||||
colorcodecs = new Vector();
|
||||
tilecodecs = new Vector();
|
||||
filefilters = new Vector();
|
||||
palettefilters = new Vector();
|
||||
filelisteners = new Vector();
|
||||
Document doc = null;
|
||||
try {
|
||||
doc = XMLParser.parse(file);
|
||||
} catch (SAXException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
if (doc == null) return;
|
||||
|
||||
tmspec = doc.getDocumentElement(); // root element (<tmspec> tag)
|
||||
|
||||
readDirectColorFormats();
|
||||
readIndexedColorFormats();
|
||||
|
||||
readPlanarTileFormats();
|
||||
readLinearTileFormats();
|
||||
readDirectColorTileFormats();
|
||||
readCompositeTileFormats();
|
||||
|
||||
readFileFilters();
|
||||
readPaletteFilters();
|
||||
readFileListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the directcolor format tags and creates directcolorcodecs for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readDirectColorFormats() {
|
||||
NodeList directColorTags = tmspec.getElementsByTagName("directcolor");
|
||||
for (int i=0; i<directColorTags.getLength(); i++) {
|
||||
Element dc = (Element)directColorTags.item(i);
|
||||
String id = dc.getAttribute("id");
|
||||
int bpp = Integer.parseInt(dc.getAttribute("bpp"));
|
||||
int rmask = (int)Long.parseLong(dc.getAttribute("rmask"), 16);
|
||||
int gmask = (int)Long.parseLong(dc.getAttribute("gmask"), 16);
|
||||
int bmask = (int)Long.parseLong(dc.getAttribute("bmask"), 16);
|
||||
int amask = 0;
|
||||
if (!dc.getAttribute("amask").equals(""))
|
||||
amask = (int)Long.parseLong(dc.getAttribute("amask"), 16);
|
||||
String desc = XMLParser.getNodeValue(dc.getElementsByTagName("description").item(0));
|
||||
colorcodecs.add(new DirectColorCodec(id, bpp, rmask, gmask, bmask, amask, desc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the indexedcolor format tags and creates indexedcolorcodecs for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readIndexedColorFormats() {
|
||||
NodeList indexedColorTags = tmspec.getElementsByTagName("indexedcolor");
|
||||
for (int i=0; i<indexedColorTags.getLength(); i++) {
|
||||
Element ic = (Element)indexedColorTags.item(i);
|
||||
String id = ic.getAttribute("id");
|
||||
int bpp = Integer.parseInt(ic.getAttribute("bpp"));
|
||||
String desc = XMLParser.getNodeValue(ic.getElementsByTagName("description").item(0));
|
||||
int endianness = ic.getAttribute("endianness").equals("little") ? ColorCodec.LITTLE_ENDIAN : ColorCodec.BIG_ENDIAN;
|
||||
String hexString = XMLParser.getNodeValue(ic.getElementsByTagName("data").item(0));
|
||||
byte[] data = HexStringConverter.hexStringToBytes(hexString);
|
||||
|
||||
// pack the data into array of 32-bit ARGB ints
|
||||
DirectColorCodec codec = new DirectColorCodec("", 24, 0xFF0000, 0x00FF00, 0x0000FF, 0, "");
|
||||
codec.setEndianness(endianness);
|
||||
int[] entries = new int[data.length / codec.getBytesPerPixel()];
|
||||
for (int j=0; j<entries.length; j++) {
|
||||
entries[j] = codec.fromBytes(data, j * codec.getBytesPerPixel());
|
||||
}
|
||||
|
||||
colorcodecs.add(new IndexedColorCodec(id, bpp, entries, desc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the planartile format tags and creates planartilecodecs for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readPlanarTileFormats() {
|
||||
NodeList planarTileTags = tmspec.getElementsByTagName("planartile");
|
||||
for (int i=0; i<planarTileTags.getLength(); i++) {
|
||||
Element plc = (Element)planarTileTags.item(i);
|
||||
String id = plc.getAttribute("id");
|
||||
int bpp = Integer.parseInt(plc.getAttribute("bpp"));
|
||||
String desc = XMLParser.getNodeValue(plc.getElementsByTagName("description").item(0));
|
||||
StringTokenizer st = new StringTokenizer(plc.getAttribute("planeorder"), ",");
|
||||
int[] ofs = new int[st.countTokens()];
|
||||
for (int j=0; j<ofs.length; j++) {
|
||||
ofs[j] = Integer.parseInt(st.nextToken());
|
||||
}
|
||||
tilecodecs.add(new PlanarTileCodec(id, ofs, desc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the lineartile tags and creates lineartilecodecs for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readLinearTileFormats() {
|
||||
NodeList linearTileTags = tmspec.getElementsByTagName("lineartile");
|
||||
for (int i=0; i<linearTileTags.getLength(); i++) {
|
||||
Element lnc = (Element)linearTileTags.item(i);
|
||||
String id = lnc.getAttribute("id");
|
||||
int bpp = Integer.parseInt(lnc.getAttribute("bpp"));
|
||||
int ordering = LinearTileCodec.IN_ORDER;
|
||||
if(lnc.getAttribute("ordering").equals("reverse"))
|
||||
ordering = LinearTileCodec.REVERSE_ORDER;
|
||||
String desc = XMLParser.getNodeValue(lnc.getElementsByTagName("description").item(0));
|
||||
tilecodecs.add(new LinearTileCodec(id, bpp, ordering, desc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the directcolortile tags and creates directcolortilecodecs for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readDirectColorTileFormats() {
|
||||
NodeList directColorTileTags = tmspec.getElementsByTagName("directcolortile");
|
||||
for (int i=0; i<directColorTileTags.getLength(); i++) {
|
||||
Element dcc = (Element)directColorTileTags.item(i);
|
||||
String id = dcc.getAttribute("id");
|
||||
// String formatID = dcc.getAttribute("colorformat"); TODO
|
||||
int bpp = Integer.parseInt(dcc.getAttribute("bpp"));
|
||||
int rmask = (int)Long.parseLong(dcc.getAttribute("rmask"), 16);
|
||||
int gmask = (int)Long.parseLong(dcc.getAttribute("gmask"), 16);
|
||||
int bmask = (int)Long.parseLong(dcc.getAttribute("bmask"), 16);
|
||||
int amask = 0;
|
||||
if (!dcc.getAttribute("amask").equals(""))
|
||||
amask = (int)Long.parseLong(dcc.getAttribute("amask"), 16);
|
||||
String desc = XMLParser.getNodeValue(dcc.getElementsByTagName("description").item(0));
|
||||
tilecodecs.add(new DirectColorTileCodec(id, bpp, rmask, gmask, bmask, amask, desc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the compositetile tags and creates compositetilecodecs for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readCompositeTileFormats() {
|
||||
NodeList compositeTileTags = tmspec.getElementsByTagName("compositetile");
|
||||
for (int i=0; i<compositeTileTags.getLength(); i++) {
|
||||
Element cpc = (Element)compositeTileTags.item(i);
|
||||
String id = cpc.getAttribute("id");
|
||||
String desc = XMLParser.getNodeValue(cpc.getElementsByTagName("description").item(0));
|
||||
// build array of planar codecs
|
||||
StringTokenizer st = new StringTokenizer(cpc.getAttribute("formats"), ",");
|
||||
TileCodec[] codecs = new TileCodec[st.countTokens()];
|
||||
int cc=0;
|
||||
int bpp=0;
|
||||
for (int j=0; j<codecs.length; j++) {
|
||||
String cid = st.nextToken();
|
||||
// find the codec with correct id
|
||||
for (int k=0; k<tilecodecs.size(); k++) {
|
||||
TileCodec tc = (TileCodec)tilecodecs.get(k);
|
||||
if (tc.getID().equals(cid)) {
|
||||
codecs[j] = tc;
|
||||
cc++;
|
||||
bpp += tc.getBitsPerPixel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cc != codecs.length) continue; // one or more codec IDs invalid
|
||||
tilecodecs.add(new CompositeTileCodec(id, bpp, codecs, desc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the filefilter tags and creates TMTileCodecFilters for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readFileFilters() {
|
||||
NodeList fftags = tmspec.getElementsByTagName("filefilter");
|
||||
for (int i=0; i<fftags.getLength(); i++) {
|
||||
Element ff = (Element)fftags.item(i);
|
||||
String extlist = ff.getAttribute("extensions");
|
||||
String desc = XMLParser.getNodeValue(ff.getElementsByTagName("description").item(0));
|
||||
String codecID = ff.getAttribute("tileformat");
|
||||
int defaultMode = TileCodec.MODE_1D;
|
||||
if (ff.getAttribute("mode").equals("2D"))
|
||||
defaultMode = TileCodec.MODE_2D;
|
||||
filefilters.add(new TMTileCodecFileFilter(extlist, desc, codecID, defaultMode));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the palettefilter tags and creates TMPaletteFilters for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readPaletteFilters() {
|
||||
NodeList pftags = tmspec.getElementsByTagName("palettefilter");
|
||||
for (int i=0; i<pftags.getLength(); i++) {
|
||||
Element pf = (Element)pftags.item(i);
|
||||
String extlist = pf.getAttribute("extensions");
|
||||
String desc = XMLParser.getNodeValue(pf.getElementsByTagName("description").item(0));
|
||||
String codecID = pf.getAttribute("colorformat");
|
||||
int size = Integer.parseInt(pf.getAttribute("size"));
|
||||
int offset = Integer.parseInt(pf.getAttribute("offset"));
|
||||
int endianness = ColorCodec.LITTLE_ENDIAN;
|
||||
if (pf.getAttribute("endianness").equals("big")) {
|
||||
endianness = ColorCodec.BIG_ENDIAN;
|
||||
}
|
||||
palettefilters.add(new TMPaletteFileFilter(extlist, desc, codecID, size, offset, endianness));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reads the filelistener tags and creates TMFileListeners for them.
|
||||
*
|
||||
**/
|
||||
|
||||
private static void readFileListeners() {
|
||||
ClassLoader loader = ClassLoader.getSystemClassLoader();
|
||||
NodeList fltags = tmspec.getElementsByTagName("filelistener");
|
||||
for (int i=0; i<fltags.getLength(); i++) {
|
||||
Element fl = (Element)fltags.item(i);
|
||||
String extlist = fl.getAttribute("extensions");
|
||||
String classname = fl.getAttribute("classname");
|
||||
Class c;
|
||||
try {
|
||||
c = loader.loadClass(classname);
|
||||
}
|
||||
catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
Object o;
|
||||
try {
|
||||
o = c.newInstance();
|
||||
}
|
||||
catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
filelisteners.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the vector of color codecs that's been created based on the XML.
|
||||
*
|
||||
**/
|
||||
|
||||
public static Vector getColorCodecs() {
|
||||
return colorcodecs;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the vector of tile codecs that's been created based on the XML.
|
||||
*
|
||||
**/
|
||||
|
||||
public static Vector getTileCodecs() {
|
||||
return tilecodecs;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the vector of file filters that's been created based on the XML.
|
||||
*
|
||||
**/
|
||||
|
||||
public static Vector getFileFilters() {
|
||||
return filefilters;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the vector of palette filters that's been created based on the XML.
|
||||
*
|
||||
**/
|
||||
|
||||
public static Vector getPaletteFilters() {
|
||||
return palettefilters;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the vector of file listeners that's been created based on the XML.
|
||||
*
|
||||
**/
|
||||
|
||||
public static Vector getFileListeners() {
|
||||
return filelisteners;
|
||||
}
|
||||
|
||||
}
|
1591
src/tm/canvases/TMEditorCanvas.java
Normal file
467
src/tm/canvases/TMPixelCanvas.java
Normal file
|
@ -0,0 +1,467 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.canvases;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides a surface where decoded graphics data can be rendered.
|
||||
* This is an abstract class: Subclasses are required to implement the
|
||||
* unpackPixels() and packPixels() methods for decoding and encoding
|
||||
* graphics data, respectively.
|
||||
* Pixels must be decoded to 32-bit ARGB format (see implementation details below).
|
||||
*
|
||||
**/
|
||||
|
||||
public abstract class TMPixelCanvas extends JPanel {
|
||||
|
||||
protected int canvasWidth;
|
||||
protected int canvasHeight;
|
||||
protected double scale;
|
||||
|
||||
protected byte[] bits; // encoded data buffer
|
||||
protected int offset; // starting offset in buffer
|
||||
|
||||
protected int[] pixels;
|
||||
protected MemoryImageSource source=null;
|
||||
private Image image;
|
||||
private DirectColorModel colorModel;
|
||||
|
||||
private boolean showPixelGrid=false;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a pixel pane using <code>bits</code> as the graphics source.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMPixelCanvas(byte[] bits) {
|
||||
super();
|
||||
this.bits = bits;
|
||||
setOffset(0);
|
||||
setLayout(null);
|
||||
colorModel = new DirectColorModel(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000);
|
||||
setBackground(Color.gray);
|
||||
setCanvasSize(0, 0);
|
||||
setScale(1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a pixel pane of the specified size and using <code>bits</code> as the graphics source.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMPixelCanvas(byte[] bits, int canvasWidth, int canvasHeight) {
|
||||
super();
|
||||
this.bits = bits;
|
||||
setOffset(0);
|
||||
setLayout(null);
|
||||
colorModel = new DirectColorModel(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000);
|
||||
setBackground(Color.gray);
|
||||
setCanvasSize(canvasWidth, canvasHeight);
|
||||
setScale(1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the size of the canvas in pixels.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setCanvasSize(int canvasWidth, int canvasHeight) {
|
||||
this.canvasWidth = canvasWidth;
|
||||
this.canvasHeight = canvasHeight;
|
||||
|
||||
// create canvas image and initialize stuff
|
||||
pixels = new int[canvasWidth*canvasHeight];
|
||||
source = new MemoryImageSource(canvasWidth, canvasHeight, colorModel, pixels, 0, canvasWidth);
|
||||
source.setAnimated(true);
|
||||
image = createImage(source);
|
||||
|
||||
setScale(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the offset into the buffer from which tile data is unpacked/packed.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the offset.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the scale.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setScale(double scale) {
|
||||
if (scale < 1.0) scale = 1.0; // minimum
|
||||
else if (scale > 32.0) scale = 32.0; // maximum
|
||||
this.scale = scale;
|
||||
|
||||
// set size
|
||||
int scaledWidth = (int)(canvasWidth*scale);
|
||||
int scaledHeight = (int)(canvasHeight*scale);
|
||||
setSize(scaledWidth, scaledHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the scale.
|
||||
*
|
||||
**/
|
||||
|
||||
public double getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Subclasses required to implement this method. It is responsible for
|
||||
* filling the canvas with pixels, by decoding data starting at
|
||||
* &bits[offset].
|
||||
*
|
||||
**/
|
||||
|
||||
protected abstract void unpackPixels();
|
||||
|
||||
/**
|
||||
*
|
||||
* Subclasses required to implement this method. It is responsible for
|
||||
* encoding the canvas's pixels into some format.
|
||||
*
|
||||
**/
|
||||
|
||||
protected abstract void packPixels();
|
||||
|
||||
/**
|
||||
*
|
||||
* Paints pixels and grid(s).
|
||||
*
|
||||
**/
|
||||
|
||||
public void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
|
||||
// draw gridlines if necessary
|
||||
if (showPixelGrid) {
|
||||
drawPixelGrid(g);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Draws pixel grid.
|
||||
*
|
||||
**/
|
||||
|
||||
private void drawPixelGrid(Graphics g) {
|
||||
if (scale < 8.0) return; // don't show it for scales less than 8
|
||||
g.setColor(Color.gray);
|
||||
// draw horizontal lines
|
||||
for (int i=1; i<canvasHeight; i++) {
|
||||
g.fillRect(0, (int)(i*scale), getWidth(), 1);
|
||||
}
|
||||
// draw vertical lines
|
||||
for (int i=1; i<canvasWidth; i++) {
|
||||
g.fillRect((int)(i*scale), 0, 1, getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Turns the pixel grid on (true) or off (false).
|
||||
*
|
||||
**/
|
||||
|
||||
public void setPixelGridVisible(boolean showPixelGrid) {
|
||||
this.showPixelGrid = showPixelGrid;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns pixel grid visibility.
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean isPixelGridVisible() {
|
||||
return showPixelGrid;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the pixel at location (x, y) in the image to the specified value.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setPixel(int x, int y, int argb) {
|
||||
pixels[(y*canvasWidth)+x] = argb;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the pixel at location (x, y) in the image.
|
||||
*
|
||||
**/
|
||||
|
||||
protected int getPixel(int x, int y) {
|
||||
return pixels[(y*canvasWidth)+x];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Inverts the pixel at (x,y).
|
||||
*
|
||||
**/
|
||||
|
||||
protected void xorPixel(int x, int y) {
|
||||
setPixel(x, y, getPixel(x, y) ^ 0xFFFFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the buffer containing the currently rendered pixels.
|
||||
*
|
||||
**/
|
||||
|
||||
protected int[] getPixels() {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the width of the canvas image.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getCanvasWidth() {
|
||||
return canvasWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the height of the canvas image.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getCanvasHeight() {
|
||||
return canvasHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the data buffer that will be used to fetch/encode native graphics data.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setBits(byte[] bits) {
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the native graphics data buffer.
|
||||
*
|
||||
**/
|
||||
|
||||
public byte[] getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Updates the pixels and repaints the canvas.
|
||||
*
|
||||
**/
|
||||
|
||||
public void redraw() {
|
||||
source.newPixels();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the image where the pixels are rendered.
|
||||
*
|
||||
**/
|
||||
|
||||
public Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Mirrors the canvas (flips horizontally).
|
||||
*
|
||||
**/
|
||||
|
||||
public void mirror() {
|
||||
for (int y=0; y<canvasHeight; y++) {
|
||||
for (int x=0; x<canvasWidth/2; x++) {
|
||||
int p1 = getPixel(x, y);
|
||||
int p2 = getPixel(canvasWidth-1-x, y);
|
||||
setPixel(x, y, p2);
|
||||
setPixel(canvasWidth-1-x, y, p1);
|
||||
}
|
||||
}
|
||||
packPixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Flips the canvas (vertically).
|
||||
*
|
||||
**/
|
||||
|
||||
public void flip() {
|
||||
for (int y=0; y<canvasHeight/2; y++) {
|
||||
for (int x=0; x<canvasWidth; x++) {
|
||||
int p1 = getPixel(x, y);
|
||||
int p2 = getPixel(x, canvasHeight-1-y);
|
||||
setPixel(x, y, p2);
|
||||
setPixel(x, canvasHeight-1-y, p1);
|
||||
}
|
||||
}
|
||||
packPixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Rotates the canvas 90 degrees left (counter-clockwise).
|
||||
*
|
||||
**/
|
||||
|
||||
public void rotateLeft() {
|
||||
int[] pix = getPixels();
|
||||
setCanvasSize(canvasHeight, canvasWidth);
|
||||
int ofs = 0;
|
||||
for (int i=0; i<canvasWidth; i++) {
|
||||
for (int j=canvasHeight-1; j>=0; j--) {
|
||||
setPixel(i, j, pix[ofs++]);
|
||||
}
|
||||
}
|
||||
packPixels();
|
||||
setScale(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Rotates the canvas 90 degrees right (clockwise).
|
||||
*
|
||||
**/
|
||||
|
||||
public void rotateRight() {
|
||||
int[] pix = getPixels();
|
||||
setCanvasSize(canvasHeight, canvasWidth);
|
||||
int ofs = 0;
|
||||
for (int i=canvasWidth-1; i>=0; i--) {
|
||||
for (int j=0; j<canvasHeight; j++) {
|
||||
setPixel(i, j, pix[ofs++]);
|
||||
}
|
||||
}
|
||||
packPixels();
|
||||
setScale(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Shifts the canvas one column left.
|
||||
*
|
||||
**/
|
||||
|
||||
public void shiftLeft() {
|
||||
for (int y=0; y<canvasHeight; y++) {
|
||||
int p0 = getPixel(0, y);
|
||||
for (int x=0; x<canvasWidth-1; x++) {
|
||||
setPixel(x, y, getPixel(x+1, y));
|
||||
}
|
||||
setPixel(canvasWidth-1, y, p0);
|
||||
}
|
||||
packPixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Shifts the canvas one column right.
|
||||
*
|
||||
**/
|
||||
|
||||
public void shiftRight() {
|
||||
for (int y=0; y<canvasHeight; y++) {
|
||||
int p0 = getPixel(canvasWidth-1, y);
|
||||
for (int x=canvasWidth-2; x>=0; x--) {
|
||||
setPixel(x+1, y, getPixel(x, y));
|
||||
}
|
||||
setPixel(0, y, p0);
|
||||
}
|
||||
packPixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Shifts the canvas one row up.
|
||||
*
|
||||
**/
|
||||
|
||||
public void shiftUp() {
|
||||
for (int x=0; x<canvasWidth; x++) {
|
||||
int p0 = getPixel(x, 0);
|
||||
for (int y=0; y<canvasHeight-1; y++) {
|
||||
setPixel(x, y, getPixel(x, y+1));
|
||||
}
|
||||
setPixel(x, canvasHeight-1, p0);
|
||||
}
|
||||
packPixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Shifts the canvas one row down.
|
||||
*
|
||||
**/
|
||||
|
||||
public void shiftDown() {
|
||||
for (int x=0; x<canvasWidth; x++) {
|
||||
int p0 = getPixel(x, canvasHeight-1);
|
||||
for (int y=canvasHeight-2; y>=0; y--) {
|
||||
setPixel(x, y+1, getPixel(x, y));
|
||||
}
|
||||
setPixel(x, 0, p0);
|
||||
}
|
||||
packPixels();
|
||||
}
|
||||
|
||||
}
|
354
src/tm/canvases/TMSelectionCanvas.java
Normal file
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.canvases;
|
||||
|
||||
import tm.TMPalette;
|
||||
import tm.tilecodecs.TileCodec;
|
||||
import tm.reversibleaction.ReversibleMoveSelectionAction;
|
||||
import tm.ui.TMUI;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* A tile canvas that can be moved around.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMSelectionCanvas extends TMTileCanvas implements MouseInputListener {
|
||||
|
||||
private TMUI ui;
|
||||
private static JPopupMenu selectionPopup = null;
|
||||
|
||||
private int dx;
|
||||
private int dy;
|
||||
private int oldX;
|
||||
private int oldY;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a selection canvas by copying the specified tile grid.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMSelectionCanvas(TMUI ui, TMTileCanvas canvas, int x1, int y1, int w, int h) {
|
||||
super(null);
|
||||
this.ui = ui;
|
||||
if (selectionPopup == null)
|
||||
initSelectionPopup();
|
||||
addMouseListener(this);
|
||||
addMouseMotionListener(this);
|
||||
setMode(TileCodec.MODE_1D); // all copied data is stored in 1-Dimensional mode
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
|
||||
setVisible(false);
|
||||
// create buffer to hold tile data
|
||||
byte[] selbits = new byte[w * h * canvas.getCodec().getTileSize()];
|
||||
// init the canvas
|
||||
setBits(selbits);
|
||||
setGridSize(w, h);
|
||||
setCodec(canvas.getCodec());
|
||||
if (canvas.getPalette() != null) {
|
||||
setPalette(new TMPalette(canvas.getPalette()));
|
||||
setPalIndex(canvas.getPalIndex());
|
||||
}
|
||||
|
||||
// copy pixels
|
||||
for (int i=0; i<h*8; i++) {
|
||||
for (int j=0; j<w*8; j++) {
|
||||
setPixel(j, i, canvas.getPixel(j+x1*8, i+y1*8));
|
||||
}
|
||||
}
|
||||
|
||||
packPixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets up the selection popup menu.
|
||||
*
|
||||
**/
|
||||
|
||||
private void initSelectionPopup() {
|
||||
selectionPopup = new JPopupMenu();
|
||||
JMenuItem menuItem;
|
||||
// Cut
|
||||
menuItem = new JMenuItem("Cut");
|
||||
menuItem.setMnemonic(KeyEvent.VK_T);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doCutCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Copy
|
||||
menuItem = new JMenuItem("Copy");
|
||||
menuItem.setMnemonic(KeyEvent.VK_C);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doCopyCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Paste
|
||||
menuItem = new JMenuItem("Paste");
|
||||
menuItem.setMnemonic(KeyEvent.VK_P);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doPasteCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Clear
|
||||
menuItem = new JMenuItem("Clear");
|
||||
menuItem.setMnemonic(KeyEvent.VK_L);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doClearCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Select All
|
||||
menuItem = new JMenuItem("Select All");
|
||||
menuItem.setMnemonic(KeyEvent.VK_S);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doSelectAllCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
//
|
||||
selectionPopup.addSeparator();
|
||||
// Mirror
|
||||
menuItem = new JMenuItem("Mirror");
|
||||
menuItem.setMnemonic(KeyEvent.VK_M);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doMirrorCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Flip
|
||||
menuItem = new JMenuItem("Flip");
|
||||
menuItem.setMnemonic(KeyEvent.VK_F);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doFlipCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
//
|
||||
selectionPopup.addSeparator();
|
||||
// Rotate Right
|
||||
menuItem = new JMenuItem("Rotate Right");
|
||||
menuItem.setMnemonic(KeyEvent.VK_O);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doRotateRightCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Rotate Left
|
||||
menuItem = new JMenuItem("Rotate Left");
|
||||
menuItem.setMnemonic(KeyEvent.VK_A);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doRotateLeftCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
//
|
||||
selectionPopup.addSeparator();
|
||||
// Shift Left
|
||||
menuItem = new JMenuItem("Shift Left");
|
||||
menuItem.setMnemonic(KeyEvent.VK_L);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doShiftLeftCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Shift Right
|
||||
menuItem = new JMenuItem("Shift Right");
|
||||
menuItem.setMnemonic(KeyEvent.VK_R);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doShiftRightCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Shift Up
|
||||
menuItem = new JMenuItem("Shift Up");
|
||||
menuItem.setMnemonic(KeyEvent.VK_U);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doShiftUpCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
// Shift Down
|
||||
menuItem = new JMenuItem("Shift Down");
|
||||
menuItem.setMnemonic(KeyEvent.VK_D);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doShiftDownCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
//
|
||||
selectionPopup.addSeparator();
|
||||
// Stretch...
|
||||
menuItem = new JMenuItem("Stretch...");
|
||||
menuItem.setMnemonic(KeyEvent.VK_E);
|
||||
menuItem.addActionListener(
|
||||
new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ui.doStretchCommand();
|
||||
}
|
||||
}
|
||||
);
|
||||
selectionPopup.add(menuItem);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Paints the selection.
|
||||
*
|
||||
**/
|
||||
|
||||
public void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
// draw frame
|
||||
g.setColor(Color.white);
|
||||
g.drawRect(0, 0, getWidth()-1, getHeight()-1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Called when user has pressed mouse button and is moving mouse.
|
||||
*
|
||||
**/
|
||||
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
// reposition
|
||||
setLocation(getX()+e.getX()-dx, getY()+e.getY()-dy);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Mouse event handlers.
|
||||
*
|
||||
**/
|
||||
|
||||
public void mouseMoved(MouseEvent e) { }
|
||||
|
||||
public void mouseClicked(MouseEvent e) { }
|
||||
|
||||
public void mouseEntered(MouseEvent e) { }
|
||||
|
||||
public void mouseExited(MouseEvent e) { }
|
||||
|
||||
public void mousePressed(MouseEvent e) {
|
||||
dx = e.getX();
|
||||
dy = e.getY();
|
||||
oldX = getX();
|
||||
oldY = getY();
|
||||
}
|
||||
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (hasMoved()) {
|
||||
snapToGrid();
|
||||
|
||||
int dim = getScaledTileDim();
|
||||
ui.getSelectedView().addReversibleAction(
|
||||
new ReversibleMoveSelectionAction(
|
||||
this,
|
||||
oldX / dim,
|
||||
oldY / dim,
|
||||
getX() / dim,
|
||||
getY() / dim
|
||||
)
|
||||
);
|
||||
}
|
||||
maybeShowPopup(e);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Checks if the selection has been moved
|
||||
*
|
||||
**/
|
||||
public boolean hasMoved()
|
||||
{
|
||||
return (oldX != getX() && oldY != getY());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Aligns the selection to a tile boundary.
|
||||
*
|
||||
**/
|
||||
|
||||
public void snapToGrid() {
|
||||
int dim = getScaledTileDim();
|
||||
// snap to atomic tile grid
|
||||
int x = getX();
|
||||
int y = getY();
|
||||
setLocation((x / dim) * dim, (y / dim) * dim); //TODO: fix the snapping
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Shows the popup menu if the mouse event was a popup trigger.
|
||||
*
|
||||
**/
|
||||
|
||||
public void maybeShowPopup(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
selectionPopup.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
583
src/tm/canvases/TMTileCanvas.java
Normal file
|
@ -0,0 +1,583 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.canvases;
|
||||
|
||||
import tm.TMPalette;
|
||||
import tm.tilecodecs.TileCodec;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* A tile canvas is a surface where a grid of atomic tiles can be rendered.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMTileCanvas extends TMPixelCanvas {
|
||||
|
||||
protected int mode=TileCodec.MODE_1D;
|
||||
|
||||
protected int[] pixdata = new int[8*8];
|
||||
|
||||
protected int cols=0;
|
||||
protected int rows=0;
|
||||
|
||||
protected TileCodec codec=null;
|
||||
protected TMPalette palette=null;
|
||||
protected int palIndex=0;
|
||||
|
||||
private boolean showTileGrid=false;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a tile pane, with <code>bits</code> as the encoded tile data source.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMTileCanvas(byte[] bits) {
|
||||
super(bits);
|
||||
this.cols = 0;
|
||||
this.rows = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a tile pane of the specified size, and with <code>bits</code>
|
||||
* as the encoded tile data source.
|
||||
* Doesn't work quite right?
|
||||
**/
|
||||
|
||||
public TMTileCanvas(byte[] bits, int cols, int rows) {
|
||||
super(bits, cols*8, rows*8);
|
||||
this.cols = cols;
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the tile mode. This determines how tile data is interpreted.
|
||||
* Valid modes are MODE_1D and MODE_2D.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the mode.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the codec that's used for decoding+encoding individual tiles.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setCodec(TileCodec codec) {
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the tile codec.
|
||||
*
|
||||
**/
|
||||
|
||||
public TileCodec getCodec() {
|
||||
return codec;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the palette that's used for mapping colors when bitsPerPixel <= 8.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setPalette(TMPalette palette) {
|
||||
this.palette = palette;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the palette.
|
||||
*
|
||||
**/
|
||||
|
||||
public TMPalette getPalette() {
|
||||
return palette;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the palette index.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setPalIndex(int palIndex) {
|
||||
this.palIndex = palIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the palette index.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getPalIndex() {
|
||||
return palIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the size of the tile canvas.
|
||||
*
|
||||
**/
|
||||
|
||||
public void setGridSize(int cols, int rows) {
|
||||
setCanvasSize(cols*8, rows*8);
|
||||
this.cols = cols;
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Encodes the specified tile. TODO
|
||||
*
|
||||
**/
|
||||
|
||||
public void packTile(int x, int y) {
|
||||
int pixOfs;
|
||||
int bitsOfs, pos;
|
||||
// encode single atomic tile
|
||||
bitsOfs = getTileBitsOffset(x, y);
|
||||
if (bitsOfs >= 0) {
|
||||
// copy pixels
|
||||
pixOfs = (y * 8 * canvasWidth) + (x * 8);
|
||||
pos = 0;
|
||||
if (codec.getBitsPerPixel() <= 8) {
|
||||
int colorCount = codec.getColorCount();
|
||||
int colorIndex = palIndex * colorCount;
|
||||
// map RGB values to palette indices
|
||||
for (int p=0; p<8; p++) {
|
||||
for (int q=0; q<8; q++) {
|
||||
pixdata[pos++] = palette.indexOf(colorIndex, pixels[pixOfs++]);
|
||||
}
|
||||
pixOfs += canvasWidth - 8;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// non-palettized: color is actual 32-bit ARGB value
|
||||
for (int p=0; p<8; p++) {
|
||||
for (int q=0; q<8; q++) {
|
||||
pixdata[pos++] = pixels[pixOfs++];
|
||||
}
|
||||
pixOfs += canvasWidth - 8;
|
||||
}
|
||||
}
|
||||
codec.encode(pixdata, bits, bitsOfs, getStride());
|
||||
}
|
||||
else {
|
||||
// not valid tile, do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the number of columns in the tile grid.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getCols() {
|
||||
return cols;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the number of rows in the tile grid.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Decodes tile data to pixel buffer.
|
||||
*
|
||||
**/
|
||||
|
||||
public void unpackPixels() {
|
||||
if (codec == null) return;
|
||||
int[] decodedTile;
|
||||
int pixOfs = 0;
|
||||
int bitsOfs, tileOfs, pos;
|
||||
int colorIndex = palIndex * codec.getColorCount(); // only valid for palettized codecs
|
||||
int bpp = codec.getBitsPerPixel();
|
||||
int stride = getStride();
|
||||
// render grid of atomic tiles
|
||||
for (int i=0; i<rows; i++) {
|
||||
for (int j=0; j<cols; j++) {
|
||||
bitsOfs = getTileBitsOffset(j, i);
|
||||
if (bitsOfs >= 0) {
|
||||
decodedTile = codec.decode(bits, bitsOfs, stride);
|
||||
// copy pixels
|
||||
tileOfs = pixOfs;
|
||||
pos = 0;
|
||||
if ((bpp <= 8) && (palette != null)) {
|
||||
// map palette indices to RGB values
|
||||
for (int p=0; p<8; p++) {
|
||||
for (int q=0; q<8; q++) {
|
||||
pixels[tileOfs++] = palette.getEntryRGB(colorIndex + decodedTile[pos++]);
|
||||
}
|
||||
tileOfs += canvasWidth - 8;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// non-palettized: color is actual 32-bit ARGB value
|
||||
for (int p=0; p<8; p++) {
|
||||
for (int q=0; q<8; q++) {
|
||||
pixels[tileOfs++] = decodedTile[pos++];
|
||||
}
|
||||
tileOfs += canvasWidth - 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// not valid tile, fill with gray pixels
|
||||
tileOfs = pixOfs;
|
||||
for (int p=0; p<8; p++) {
|
||||
for (int q=0; q<8; q++) {
|
||||
pixels[tileOfs++] = 0x7F7F7F;
|
||||
}
|
||||
tileOfs += canvasWidth - 8;
|
||||
}
|
||||
}
|
||||
pixOfs += 8; // next tile column
|
||||
}
|
||||
pixOfs += canvasWidth*7; // next tile row
|
||||
}
|
||||
source.newPixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Encodes tile data.
|
||||
*
|
||||
**/
|
||||
|
||||
public void packPixels() {
|
||||
if (codec == null) return;
|
||||
int pixOfs = 0;
|
||||
int bitsOfs, tileOfs, pos;
|
||||
int colorCount = codec.getColorCount(); // only valid for palettized codecs
|
||||
int colorIndex = palIndex * colorCount;
|
||||
int bpp = codec.getBitsPerPixel();
|
||||
int stride = getStride();
|
||||
// encode grid of atomic tiles
|
||||
for (int i=0; i<rows; i++) {
|
||||
for (int j=0; j<cols; j++) {
|
||||
bitsOfs = getTileBitsOffset(j, i);
|
||||
if (bitsOfs >= 0) {
|
||||
// copy pixels
|
||||
tileOfs = pixOfs;
|
||||
pos = 0;
|
||||
if ((bpp <= 8) && (palette != null)) {
|
||||
// map RGB values to palette indices
|
||||
for (int p=0; p<8; p++) {
|
||||
for (int q=0; q<8; q++) {
|
||||
pixdata[pos++] = palette.indexOf(colorIndex, pixels[tileOfs++]);
|
||||
}
|
||||
tileOfs += canvasWidth - 8;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// non-palettized: color is actual 32-bit ARGB value
|
||||
for (int p=0; p<8; p++) {
|
||||
for (int q=0; q<8; q++) {
|
||||
pixdata[pos++] = pixels[tileOfs++];
|
||||
}
|
||||
tileOfs += canvasWidth - 8;
|
||||
}
|
||||
}
|
||||
codec.encode(pixdata, bits, bitsOfs, stride);
|
||||
}
|
||||
else {
|
||||
// not valid tile, do nothing
|
||||
}
|
||||
pixOfs += 8; // next tile column
|
||||
}
|
||||
pixOfs += canvasWidth*7; // next tile row
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Paints tiles and grid.
|
||||
*
|
||||
**/
|
||||
|
||||
public void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
// draw gridlines if necessary
|
||||
if (showTileGrid) {
|
||||
drawTileGrid(g);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Draws atomic tile grid.
|
||||
*
|
||||
**/
|
||||
|
||||
private void drawTileGrid(Graphics g) {
|
||||
g.setColor(Color.red); // gridline color
|
||||
// draw horizontal lines
|
||||
for (int i=1; i<rows; i++) {
|
||||
g.fillRect(0, (int)(i*scale*8), getWidth(), 1);
|
||||
}
|
||||
// draw vertical lines
|
||||
for (int i=1; i<cols; i++) {
|
||||
g.fillRect((int)(i*scale*8), 0, 1, getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the length of a side (in pixels) in an 8x8 tile, subject to the current scaling.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getScaledTileDim() {
|
||||
return (int)(scale*8);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Turns the tile grid on (true) or off (false).
|
||||
*
|
||||
**/
|
||||
|
||||
public void setTileGridVisible(boolean showTileGrid) {
|
||||
this.showTileGrid = showTileGrid;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns tile grid visibility.
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean isTileGridVisible() {
|
||||
return showTileGrid;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the starting offset of the data for the tile at position (x,y)
|
||||
* in the grid.
|
||||
*
|
||||
**/
|
||||
|
||||
protected int getTileBitsOffset(int x, int y) {
|
||||
int relOfs = (y * getRowSize()) + (x * getTileIncrement());
|
||||
int absOfs = relOfs + offset;
|
||||
// range check
|
||||
int limit = 0;
|
||||
if (mode == TileCodec.MODE_1D) {
|
||||
limit = bits.length - codec.getTileSize();
|
||||
}
|
||||
else {
|
||||
limit = bits.length - getRowIncrement();
|
||||
}
|
||||
if (absOfs <= limit) return absOfs;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the size of one row of tiles.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getRowSize() {
|
||||
if (codec != null)
|
||||
return cols * codec.getTileSize();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the size of one page of tiles (entire tile grid).
|
||||
*
|
||||
**/
|
||||
|
||||
public int getPageSize() {
|
||||
if (codec != null)
|
||||
return rows * getRowSize();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the amount to increment data offset per tile.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getTileIncrement() {
|
||||
return (mode == TileCodec.MODE_1D) ? codec.getTileSize() : codec.getBytesPerRow();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the amount to increment data offset per row.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getRowIncrement() {
|
||||
return codec.getTileSize() * cols;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the amount to increment data offset per page.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getPageIncrement() {
|
||||
return getRowIncrement() * rows;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the stride.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getStride() {
|
||||
return (mode == TileCodec.MODE_1D) ? 0 : cols-1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Stretches the canvas from current (cols,rows) to specified (cols,rows).
|
||||
*
|
||||
**/
|
||||
|
||||
public void stretchTo(int cols, int rows) {
|
||||
// get current pixels & dimensions
|
||||
int[] pix = getPixels();
|
||||
int oldW = getCanvasWidth();
|
||||
int oldH = getCanvasHeight();
|
||||
|
||||
// set new size
|
||||
setGridSize(cols, rows);
|
||||
int w = getCanvasWidth();
|
||||
int h = getCanvasHeight();
|
||||
|
||||
// create buffer to hold tile data
|
||||
byte[] selbits = new byte[cols * rows * codec.getTileSize()];
|
||||
setBits(selbits);
|
||||
|
||||
// calculate step size for each dimension
|
||||
double stepX = (double)oldW / (double)w;
|
||||
double stepY = (double)oldH / (double)h;
|
||||
|
||||
// stretch the old image onto the new canvas
|
||||
double x;
|
||||
double y=0;
|
||||
for (int i=0; i<h; i++) {
|
||||
x=0;
|
||||
for (int j=0; j<w; j++) {
|
||||
int p = pix[(((int)y)*oldW)+(int)x];
|
||||
setPixel(j, i, p);
|
||||
x += stepX;
|
||||
}
|
||||
y += stepY;
|
||||
}
|
||||
packPixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Rotates the grid of tiles 90 degrees left (counter-clockwise).
|
||||
*
|
||||
**/
|
||||
|
||||
public void rotateLeft() {
|
||||
int[] pix = getPixels();
|
||||
setGridSize(rows, cols);
|
||||
int ofs = 0;
|
||||
for (int i=0; i<canvasWidth; i++) {
|
||||
for (int j=canvasHeight-1; j>=0; j--) {
|
||||
setPixel(i, j, pix[ofs++]);
|
||||
}
|
||||
}
|
||||
packPixels();
|
||||
setScale(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Rotates the grid of tiles 90 degrees right (clockwise).
|
||||
*
|
||||
**/
|
||||
|
||||
public void rotateRight() {
|
||||
int[] pix = getPixels();
|
||||
setGridSize(rows, cols);
|
||||
int ofs = 0;
|
||||
for (int i=canvasWidth-1; i>=0; i--) {
|
||||
for (int j=0; j<canvasHeight; j++) {
|
||||
setPixel(i, j, pix[ofs++]);
|
||||
}
|
||||
}
|
||||
packPixels();
|
||||
setScale(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Copies a tile.
|
||||
*
|
||||
**/
|
||||
|
||||
public void copyTile(byte[] from, byte[] to, int offsetFrom, int offsetTo, int strideFrom, int strideTo, int bytesPerRow) {
|
||||
for (int i=0; i<8; i++) {
|
||||
// copy one row
|
||||
for (int j=0; j<bytesPerRow; j++) {
|
||||
to[offsetTo++] = from[offsetFrom++];
|
||||
}
|
||||
offsetFrom += strideFrom;
|
||||
offsetTo += strideTo;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
186
src/tm/colorcodecs/ColorCodec.java
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.colorcodecs;
|
||||
|
||||
/**
|
||||
*
|
||||
* Abstract superclass for color codecs.
|
||||
* A "color codec" defines how values of a particular format are translated
|
||||
* to 32-bit ARGB pixel values (decoded), and back again (encoded).
|
||||
*
|
||||
**/
|
||||
|
||||
public abstract class ColorCodec {
|
||||
|
||||
public static final int LITTLE_ENDIAN=1;
|
||||
public static final int BIG_ENDIAN=2;
|
||||
public static final int MIDDLE_ENDIAN=3;
|
||||
|
||||
protected int bitsPerPixel;
|
||||
protected int bytesPerPixel;
|
||||
protected String id;
|
||||
protected String description;
|
||||
|
||||
private static int endianness;
|
||||
private int startShift;
|
||||
private int shiftStep;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a ColorCodec with the given id, bits per pixel and description.
|
||||
*
|
||||
**/
|
||||
|
||||
public ColorCodec(String id, int bitsPerPixel, String description) {
|
||||
this.id = id;
|
||||
this.bitsPerPixel = bitsPerPixel;
|
||||
this.description = description;
|
||||
bytesPerPixel = getBytesRequired(bitsPerPixel);
|
||||
setEndianness(LITTLE_ENDIAN); // default
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the endianness.
|
||||
* It determines the byte order related to methods toBytes() and fromBytes().
|
||||
**/
|
||||
|
||||
public void setEndianness(int endianness) {
|
||||
this.endianness = endianness;
|
||||
if (endianness == LITTLE_ENDIAN) {
|
||||
startShift = 0;
|
||||
shiftStep = 8;
|
||||
}
|
||||
else {
|
||||
// BIG_ENDIAN
|
||||
startShift = (bytesPerPixel-1) * 8;
|
||||
shiftStep = -8;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the ID.
|
||||
*
|
||||
**/
|
||||
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the number of bits per pixel.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getBitsPerPixel() {
|
||||
return bitsPerPixel;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the number of bytes required to hold one pixel.
|
||||
*
|
||||
**/
|
||||
|
||||
public int getBytesPerPixel() {
|
||||
return bytesPerPixel;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the description.
|
||||
*
|
||||
**/
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This method takes a value assumed to be in the color codec's native format
|
||||
* and returns the equivalent 32-bit ARGB value.
|
||||
*
|
||||
**/
|
||||
|
||||
public abstract int decode(int value);
|
||||
|
||||
/**
|
||||
*
|
||||
* This method takes a value assumed to be in 32-bit ARGB format and returns
|
||||
* the equivalent native format value.
|
||||
*
|
||||
**/
|
||||
|
||||
public abstract int encode(int argb);
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts given value to a series of bytes, according to current endianness.
|
||||
* @param bytes Array to store resulting bytes in
|
||||
* @param offset Starting offset in array
|
||||
*
|
||||
**/
|
||||
|
||||
public byte[] toBytes(int value, byte[] bytes, int offset) {
|
||||
int shift = startShift;
|
||||
for (int i=0; i<bytesPerPixel; i++) {
|
||||
bytes[offset+i] = (byte)(value >> shift);
|
||||
shift += shiftStep;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts bytes from given array to a value, according to current endianness.
|
||||
* @param offset Where to start reading the bytes that make up the value.
|
||||
*
|
||||
**/
|
||||
|
||||
public int fromBytes(byte[] bytes, int offset) {
|
||||
int shift = startShift;
|
||||
int value = 0;
|
||||
for (int i=0; i<bytesPerPixel; i++) {
|
||||
value |= (bytes[offset+i] & 0xFF) << shift;
|
||||
shift += shiftStep;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the least number of whole bytes that are required to store
|
||||
* <code>bits</code> bits of information.
|
||||
*
|
||||
**/
|
||||
|
||||
private static int getBytesRequired(int bits) {
|
||||
int bytes = bits / 8;
|
||||
int extrabits = bits % 8;
|
||||
if (extrabits != 0) bytes++;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return description;
|
||||
}
|
||||
|
||||
}
|
169
src/tm/colorcodecs/DirectColorCodec.java
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.colorcodecs;
|
||||
|
||||
/**
|
||||
*
|
||||
* A DirectColorCodec translates pixels exactly the same way as
|
||||
* a direct-color tile codec.
|
||||
* The composition of a pixel is described by Red, Green,
|
||||
* Blue and Alpha masks.
|
||||
*
|
||||
**/
|
||||
|
||||
public class DirectColorCodec extends ColorCodec {
|
||||
|
||||
private int rmask; // bitmask for Red component
|
||||
private int gmask; // bitmask for Green component
|
||||
private int bmask; // bitmask for Blue component
|
||||
private int amask; // bitmask for Alpha component
|
||||
|
||||
// how much each component must be shifted to be transformed to a 32-bit ARGB int-packed Java pixel.
|
||||
// these are pre-calculated in the constructor and used for decoding/encoding individual pixels.
|
||||
private int rshift;
|
||||
private int gshift;
|
||||
private int bshift;
|
||||
private int ashift;
|
||||
|
||||
public DirectColorCodec(String id, int bitsPerPixel, int rmask, int gmask, int bmask, int amask, String description) {
|
||||
super(id, bitsPerPixel, description);
|
||||
this.rmask = rmask;
|
||||
this.gmask = gmask;
|
||||
this.bmask = bmask;
|
||||
this.amask = amask;
|
||||
// calculate the shifts
|
||||
rshift = 23 - msb(rmask);
|
||||
gshift = 15 - msb(gmask);
|
||||
bshift = 7 - msb(bmask);
|
||||
ashift = 31 - msb(amask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the most significant set bit in the given int.
|
||||
**/
|
||||
|
||||
private static int msb(int mask) {
|
||||
for (int i=31; i>=0; i--) {
|
||||
if ((mask & 0x80000000) != 0) {
|
||||
return i;
|
||||
}
|
||||
mask <<= 1;
|
||||
}
|
||||
return -1; // no bits set
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public int decode(int value) {
|
||||
int r, g, b, a;
|
||||
// decode R component
|
||||
r = value & rmask;
|
||||
if (rshift < 0) {
|
||||
r >>= -rshift;
|
||||
}
|
||||
else {
|
||||
r <<= rshift;
|
||||
}
|
||||
|
||||
// decode G component
|
||||
g = value & gmask;
|
||||
if (gshift < 0) {
|
||||
g >>= -gshift;
|
||||
}
|
||||
else {
|
||||
g <<= gshift;
|
||||
}
|
||||
|
||||
// decode B component
|
||||
b = value & bmask;
|
||||
if (bshift < 0) {
|
||||
b >>= -bshift;
|
||||
}
|
||||
else {
|
||||
b <<= bshift;
|
||||
}
|
||||
|
||||
// decode A component
|
||||
a = value & amask;
|
||||
if (ashift < 0) {
|
||||
a >>= -ashift;
|
||||
}
|
||||
else {
|
||||
a <<= ashift;
|
||||
}
|
||||
|
||||
// final pixel
|
||||
return (a | r | g | b);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public int encode(int argb) {
|
||||
int r, g, b, a;
|
||||
// encode R component
|
||||
r = argb;
|
||||
if (rshift < 0) {
|
||||
r <<= -rshift;
|
||||
}
|
||||
else {
|
||||
r >>= rshift;
|
||||
}
|
||||
r &= rmask;
|
||||
|
||||
// encode G component
|
||||
g = argb;
|
||||
if (gshift < 0) {
|
||||
g <<= -gshift;
|
||||
}
|
||||
else {
|
||||
g >>= gshift;
|
||||
}
|
||||
g &= gmask;
|
||||
|
||||
// encode B component
|
||||
b = argb;
|
||||
if (bshift < 0) {
|
||||
b <<= -bshift;
|
||||
}
|
||||
else {
|
||||
b >>= bshift;
|
||||
}
|
||||
b &= bmask;
|
||||
|
||||
// encode A component
|
||||
a = argb;
|
||||
if (ashift < 0) {
|
||||
a <<= -ashift;
|
||||
}
|
||||
else {
|
||||
a >>= ashift;
|
||||
}
|
||||
a &= amask;
|
||||
|
||||
// final value
|
||||
return (a | r | g | b);
|
||||
}
|
||||
|
||||
}
|
75
src/tm/colorcodecs/IndexedColorCodec.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.colorcodecs;
|
||||
|
||||
/**
|
||||
*
|
||||
* An IndexedColorCodec translates pixels according to a
|
||||
* pre-defined array of 32-bit ARGB values.
|
||||
*
|
||||
**/
|
||||
|
||||
public class IndexedColorCodec extends ColorCodec {
|
||||
|
||||
private int[] colorTable; // table of pre-defined ARGB values
|
||||
|
||||
public IndexedColorCodec(String id, int bitsPerPixel, int[] colorTable, String description) {
|
||||
super(id, bitsPerPixel, description);
|
||||
this.colorTable = colorTable;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* To decode, simply look up the table.
|
||||
*
|
||||
**/
|
||||
|
||||
public int decode(int value) {
|
||||
if (value < colorTable.length) {
|
||||
return colorTable[value];
|
||||
}
|
||||
return 0; // undefined
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* To encode, find the closest match in the table.
|
||||
*
|
||||
**/
|
||||
|
||||
public int encode(int argb) {
|
||||
int targetR = (argb & 0x00FF0000) >> 16;
|
||||
int targetG = (argb & 0x0000FF00) >> 8;
|
||||
int targetB = (argb & 0x000000FF);
|
||||
int bestEntry=0, bestDiff=1000000;
|
||||
for (int i=0; i<colorTable.length; i++) {
|
||||
int val = colorTable[i];
|
||||
int r = (val & 0x00FF0000) >> 16;
|
||||
int g = (val & 0x0000FF00) >> 8;
|
||||
int b = (val & 0x000000FF);
|
||||
int diff = Math.abs(targetR - r) + Math.abs(targetG - g) + Math.abs(targetB - b);
|
||||
if (diff < bestDiff) {
|
||||
bestDiff = diff;
|
||||
bestEntry = i;
|
||||
}
|
||||
}
|
||||
return bestEntry;
|
||||
}
|
||||
|
||||
}
|
104
src/tm/filelistener/GameBoyAdvanceFileListener.java
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.filelistener;
|
||||
|
||||
/**
|
||||
*
|
||||
* Listener for Game Boy Advance (*.gba) files.
|
||||
*
|
||||
**/
|
||||
|
||||
public class GameBoyAdvanceFileListener extends TMFileListener {
|
||||
|
||||
private static final int[] nintendoLogo = {
|
||||
0x24,0xFF,0xAE,0x51,0x69,0x9A,0xA2,0x21,0x3D,0x84,0x82,0x0A,0x84,0xE4,0x09,0xAD,
|
||||
0x11,0x24,0x8B,0x98,0xC0,0x81,0x7F,0x21,0xA3,0x52,0xBE,0x19,0x93,0x09,0xCE,0x20,
|
||||
0x10,0x46,0x4A,0x4A,0xF8,0x27,0x31,0xEC,0x58,0xC7,0xE8,0x33,0x82,0xE3,0xCE,0xBF,
|
||||
0x85,0xF4,0xDF,0x94,0xCE,0x4B,0x09,0xC1,0x94,0x56,0x8A,0xC0,0x13,0x72,0xA7,0xFC,
|
||||
0x9F,0x84,0x4D,0x73,0xA3,0xCA,0x9A,0x61,0x58,0x97,0xA3,0x27,0xFC,0x03,0x98,0x76,
|
||||
0x23,0x1D,0xC7,0x61,0x03,0x04,0xAE,0x56,0xBF,0x38,0x84,0x00,0x40,0xA7,0x0E,0xFD,
|
||||
0xFF,0x52,0xFE,0x03,0x6F,0x95,0x30,0xF1,0x97,0xFB,0xC0,0x85,0x60,0xD6,0x80,0x25,
|
||||
0xA9,0x63,0xBE,0x03,0x01,0x4E,0x38,0xE2,0xF9,0xA2,0x34,0xFF,0xBB,0x3E,0x03,0x44,
|
||||
0x78,0x00,0x90,0xCB,0x88,0x11,0x3A,0x94,0x65,0xC0,0x7C,0x63,0x87,0xF0,0x3C,0xAF,
|
||||
0xD6,0x25,0xE4,0x8B,0x38,0x0A,0xAC,0x72,0x21,0xD4,0xF8,0x07
|
||||
} ;
|
||||
|
||||
/**
|
||||
*
|
||||
* Detects if this is a Game Boy Advance file.
|
||||
* The critera are that the extension is gba, that the Nintendo logo
|
||||
* character data is correct and that the byte at 0xB2 equals 0x96.
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean doFormatDetect(final byte[] data, String extension) {
|
||||
// verify extension
|
||||
if (!extension.equals("gba")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// verify Nintendo Logo Character Data
|
||||
for (int i=0; i<nintendoLogo.length; i++) {
|
||||
if ((data[0x004+i] & 0xFF) != nintendoLogo[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((data[0xB2] & 0xFF) != 0x96) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Does nothing on file load.
|
||||
*
|
||||
**/
|
||||
|
||||
public void fileLoaded(byte[] data, String extension) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Recalculates the complement check and checksum on file save.
|
||||
*
|
||||
**/
|
||||
|
||||
public void fileSaving(byte[] data, String extension) {
|
||||
// update complement check
|
||||
int complementCheck = 0xE7;
|
||||
for(int i=0xA0; i<=0xBC; i++) {
|
||||
complementCheck -= data[i] & 0xFF;
|
||||
}
|
||||
data[0xBD] = (byte)(complementCheck & 0xFF);
|
||||
|
||||
// update checksum
|
||||
int checkSum = 0;
|
||||
data[0xBE] = 0;
|
||||
data[0xBF] = 0;
|
||||
for (int i=0; i<data.length; i++) {
|
||||
checkSum += data[i] & 0xFF;
|
||||
}
|
||||
data[0xBE] = (byte)((checkSum >> 8) & 0xFF);
|
||||
data[0xBF] = (byte)(checkSum & 0xFF);
|
||||
}
|
||||
|
||||
}
|
156
src/tm/filelistener/GameBoyFileListener.java
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.filelistener;
|
||||
|
||||
/**
|
||||
*
|
||||
* Listener for Game Boy (*.gb, *.gbc) files.
|
||||
*
|
||||
**/
|
||||
|
||||
public class GameBoyFileListener extends TMFileListener {
|
||||
|
||||
private static final int[] scrollingNintendoGraphic = {
|
||||
0xCE,0xED,0x66,0x66,0xCC,0x0D,0x00,0x0B,0x03,0x73,0x00,0x83,0x00,0x0C,0x00,0x0D,
|
||||
0x00,0x08,0x11,0x1F,0x88,0x89,0x00,0x0E,0xDC,0xCC,0x6E,0xE6,0xDD,0xDD,0xD9,0x99,
|
||||
0xBB,0xBB,0x67,0x63,0x6E,0x0E,0xEC,0xCC,0xDD,0xDC,0x99,0x9F,0xBB,0xB9,0x33,0x3E
|
||||
};
|
||||
|
||||
// cartridge types (TODO: add more types)
|
||||
private static final int ROM_ONLY = 0x00;
|
||||
private static final int ROM_MBC1 = 0x01;
|
||||
private static final int ROM_MBC1_RAM = 0x02;
|
||||
private static final int ROM_MBC1_RAM_BATTERY = 0x03;
|
||||
private static final int ROM_MBC2 = 0x05;
|
||||
private static final int ROM_MBC2_BATTERY = 0x06;
|
||||
private static final int ROM_RAM = 0x08;
|
||||
private static final int ROM_RAM_BATTERY = 0x09;
|
||||
private static final int ROM_HUC1_RAM_BATTERY = 0xFF;
|
||||
|
||||
// cartridge sizes
|
||||
private static final int SIZE_256K = 0x00;
|
||||
private static final int SIZE_512K = 0x01;
|
||||
private static final int SIZE_1M = 0x02;
|
||||
private static final int SIZE_2M = 0x03;
|
||||
private static final int SIZE_4M = 0x04;
|
||||
private static final int SIZE_8M = 0x05;
|
||||
private static final int SIZE_16M = 0x06;
|
||||
private static final int SIZE_9M = 0x52;
|
||||
private static final int SIZE_10M = 0x53;
|
||||
private static final int SIZE_12M = 0x54;
|
||||
|
||||
/**
|
||||
*
|
||||
* Detects if this is a GameBoy file.
|
||||
* The criteria is that the extension is either gb, gbc or sgb and that
|
||||
* the scrolling Nintendo graphic data is correct.
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean doFormatDetect(final byte[] data, String extension) {
|
||||
// verify extension
|
||||
if (!(extension.equals("gb")
|
||||
|| extension.equals("gbc")
|
||||
|| extension.equals("sgb"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// verify scrolling Nintendo graphic
|
||||
for (int i=0; i<scrollingNintendoGraphic.length; i++) {
|
||||
if ((data[0x104+i] & 0xFF) != scrollingNintendoGraphic[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// verify cartridge type
|
||||
/* int ct = data[0x147] & 0xFF;
|
||||
switch (ct) {
|
||||
case ROM_ONLY:
|
||||
case ROM_MBC1:
|
||||
case ROM_MBC1_RAM:
|
||||
case ROM_MBC1_RAM_BATTERY:
|
||||
case ROM_MBC2:
|
||||
case ROM_MBC2_BATTERY:
|
||||
case ROM_RAM:
|
||||
case ROM_RAM_BATTERY:
|
||||
case ROM_HUC1_RAM_BATTERY:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
// verify ROM size in header
|
||||
int rs = data[0x148] & 0xFF;
|
||||
switch (rs) {
|
||||
case SIZE_256K:
|
||||
case SIZE_512K:
|
||||
case SIZE_1M:
|
||||
case SIZE_2M:
|
||||
case SIZE_4M:
|
||||
case SIZE_8M:
|
||||
case SIZE_16M:
|
||||
case SIZE_9M:
|
||||
case SIZE_10M:
|
||||
case SIZE_12M:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: verify actual file size
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Does nothing on file load.
|
||||
*
|
||||
**/
|
||||
|
||||
public void fileLoaded(byte[] data, String extension) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Recalculates the complement check and checksum on file save.
|
||||
*
|
||||
**/
|
||||
|
||||
public void fileSaving(byte[] data, String extension) {
|
||||
// update complement check
|
||||
int r = 25;
|
||||
for(int i=0x134; i<0x14D; i++) {
|
||||
r += data[i] & 0xFF;
|
||||
}
|
||||
data[0x14D] = (byte)(0x100-r);
|
||||
|
||||
// update checksum
|
||||
int checkSum = 0;
|
||||
data[0x14E] = 0;
|
||||
data[0x14F] = 0;
|
||||
int len = data.length & 0x0FFF8000;
|
||||
for (int i=0; i<len; i++) {
|
||||
checkSum += data[i] & 0xFF;
|
||||
}
|
||||
data[0x14E] = (byte)((checkSum >> 8) & 0xFF);
|
||||
data[0x14F] = (byte)(checkSum & 0xFF);
|
||||
}
|
||||
|
||||
}
|
50
src/tm/filelistener/INESFileListener.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.filelistener;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public class INESFileListener extends TMFileListener {
|
||||
|
||||
private static int[] iNES_ID = { 0x4E,0x45,0x53,0x1A };
|
||||
|
||||
public boolean doFormatDetect(final byte[] data, String extension) {
|
||||
// verify extension
|
||||
if (!extension.equals("nes")) {
|
||||
return false;
|
||||
}
|
||||
// verify NES^Z
|
||||
for (int i=0; i<iNES_ID.length; i++) {
|
||||
if ((data[i] & 0xFF) != iNES_ID[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void fileLoaded(byte[] data, String extension) {
|
||||
}
|
||||
|
||||
public void fileSaving(byte[] data, String extension) {
|
||||
}
|
||||
|
||||
}
|
176
src/tm/filelistener/SegaGenesisFileListener.java
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.filelistener;
|
||||
|
||||
/**
|
||||
*
|
||||
* Listener for Sega Genesis / Master System / 32X (*.smd) files.
|
||||
*
|
||||
**/
|
||||
|
||||
public class SegaGenesisFileListener extends TMFileListener {
|
||||
|
||||
private static final int[] sega_Mega_Drive_ = {
|
||||
0x53,0x45,0x47,0x41,0x20,0x4D,0x45,0x47,0x41,0x20,0x44,0x52,0x49,0x56,0x45,0x20
|
||||
};
|
||||
|
||||
private static final int[] sega_Genesis____ = {
|
||||
0x53,0x45,0x47,0x41,0x20,0x47,0x45,0x4E,0x45,0x53,0x49,0x53,0x20,0x20,0x20,0x20
|
||||
} ;
|
||||
|
||||
private static final int[] sega_32X________ = {
|
||||
0x53,0x45,0x47,0x41,0x20,0x33,0x32,0x58,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20
|
||||
} ;
|
||||
|
||||
private static final int[][] console_IDs = {
|
||||
sega_Mega_Drive_,
|
||||
sega_Genesis____,
|
||||
sega_32X________
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Detect if this is a Sega Genesis / Master System / 32X file.
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean doFormatDetect(final byte[] data, String extension) {
|
||||
// verify extension
|
||||
if (!(extension.equals("smd")
|
||||
|| extension.equals("md"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extension.equals("smd")) {
|
||||
int[] offsets = {
|
||||
0x2280, 0x0280, 0x2281, 0x0281, 0x2282, 0x0282, 0x2283, 0x0283,
|
||||
0x2284, 0x0284, 0x2285, 0x0285, 0x2286, 0x0286, 0x2287, 0x0287
|
||||
};
|
||||
boolean id_OK = false;
|
||||
for (int i=0; i<console_IDs.length; i++) {
|
||||
int[] id_String = console_IDs[i];
|
||||
int diff = 0;
|
||||
for (int j=0; j<id_String.length; j++) {
|
||||
diff += Math.abs((data[offsets[j]] & 0xFF) - id_String[j]);
|
||||
}
|
||||
if (diff == 0) {
|
||||
id_OK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!id_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// md extension
|
||||
}
|
||||
|
||||
// verify file size
|
||||
if (((data.length-512) % 16384) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// (data[8] & 0xFF) == 0xAA
|
||||
// (data[9] & 0xFF) == 0xBB
|
||||
// (data[10] & 0xFF) == 0x06
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Deinterleaves the data on file load.
|
||||
*
|
||||
**/
|
||||
|
||||
public void fileLoaded(byte[] data, String extension) {
|
||||
if (extension.equals("smd")) {
|
||||
byte[] block = new byte[16384];
|
||||
int blockCount = (data.length-512) / 16384;
|
||||
for (int i=0; i<blockCount; i++) {
|
||||
// decode a block
|
||||
int ofs = (i * 16384) + 512;
|
||||
// odd - 0..8191
|
||||
for (int j=1; j<16384; j+=2) {
|
||||
block[j] = data[ofs++];
|
||||
}
|
||||
// even - 8192..16383
|
||||
for (int j=0; j<16384; j+=2) {
|
||||
block[j] = data[ofs++];
|
||||
}
|
||||
// store the decoded block
|
||||
ofs = (i * 16384) + 512;
|
||||
for (int j=0; j<16384; j++) {
|
||||
data[ofs+j] = block[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// md extension
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reinterleaves the data on file save.
|
||||
*
|
||||
**/
|
||||
|
||||
public void fileSaving(byte[] data, String extension) {
|
||||
if (extension.equals("smd")) {
|
||||
// update checksum
|
||||
int checkSum = 0;
|
||||
for (int i=1024; i<data.length; i+=2) {
|
||||
checkSum += (data[i] & 0xFF) << 8;
|
||||
checkSum += data[i+1] & 0xFF;
|
||||
}
|
||||
data[0x38E] = (byte)((checkSum >> 8) & 0xFF);
|
||||
data[0x38F] = (byte)(checkSum & 0xFF);
|
||||
|
||||
// reinterleave data
|
||||
byte[] block = new byte[16384];
|
||||
int blockCount = (data.length-512) / 16384;
|
||||
for (int i=0; i<blockCount; i++) {
|
||||
// encode a block
|
||||
// odd - 0..8191
|
||||
int ofs = (i * 16384) + 512 + 1;
|
||||
for (int j=0; j<8192; j++) {
|
||||
block[j] = data[ofs];
|
||||
ofs += 2;
|
||||
}
|
||||
// even - 8192..16383
|
||||
ofs = (i * 16384) + 512;
|
||||
for (int j=8192; j<16384; j++) {
|
||||
block[j] = data[ofs];
|
||||
ofs += 2;
|
||||
}
|
||||
// store the encoded block
|
||||
ofs = (i * 16384) + 512;
|
||||
for (int j=0; j<16384; j++) {
|
||||
data[ofs+j] = block[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// md extension
|
||||
}
|
||||
}
|
||||
|
||||
}
|
84
src/tm/filelistener/SegaMasterSystemFileListener.java
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.filelistener;
|
||||
|
||||
/**
|
||||
*
|
||||
* Listener for Sega Master System (*.sms) files.
|
||||
*
|
||||
**/
|
||||
|
||||
public class SegaMasterSystemFileListener extends TMFileListener {
|
||||
|
||||
private static final int[] tmr_Sega = { 0x54,0x4D,0x52,0x20,0x53,0x45,0x47,0x41 };
|
||||
|
||||
/**
|
||||
*
|
||||
* Detects if this is a Sega Master System file.
|
||||
* The criteria are that the extension is either sms or gg and that the string
|
||||
* "TMR SEGA" appears at offset 0x7FF0.
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean doFormatDetect(final byte[] data, String extension) {
|
||||
// verify extension
|
||||
if (!(extension.equals("sms")
|
||||
|| extension.equals("gg"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// verify "TMR SEGA" string
|
||||
for (int i=0; i<tmr_Sega.length; i++) {
|
||||
if ((data[0x7FF0+i] & 0xFF) != tmr_Sega[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Does nothing on file load.
|
||||
*
|
||||
**/
|
||||
|
||||
public void fileLoaded(byte[] data, String extension) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Recalculates the checksum on file save.
|
||||
*
|
||||
**/
|
||||
|
||||
public void fileSaving(byte[] data, String extension) {
|
||||
// update checksum
|
||||
int checkSum = 0;
|
||||
for (int i=0; i<0x7FF0; i++) {
|
||||
checkSum += data[i] & 0xFF;
|
||||
}
|
||||
for (int i=0x8000; i<data.length; i++) {
|
||||
checkSum += data[i] & 0xFF;
|
||||
}
|
||||
data[0x7FFA] = (byte)(checkSum & 0xFF);
|
||||
data[0x7FFB] = (byte)((checkSum >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
}
|
64
src/tm/filelistener/TMFileListener.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.filelistener;
|
||||
|
||||
/**
|
||||
*
|
||||
* Abstract class that defines the interface for filelisteners.
|
||||
* A <code>filelistener</code> is an object that is notified when a file has
|
||||
* been fully loaded into memory, as well as when it is about to be saved.
|
||||
* At these times it may perform various operations on the data being saved,
|
||||
* such as repairing checksums.
|
||||
* The fileformatlistener also has to implement a method that determines if
|
||||
* the file being saved is indeed of a supported format. This usually involves
|
||||
* checking the header (verifying ID strings and such).
|
||||
*
|
||||
**/
|
||||
|
||||
public abstract class TMFileListener {
|
||||
|
||||
/**
|
||||
*
|
||||
* This method is invoked when a file has been loaded, to give the file
|
||||
* listener a chance to detect the file format. If it returns <code>true</code>,
|
||||
* this file listener will receive all subsequent fileLoaded() and fileSaved()
|
||||
* events for the file.
|
||||
*
|
||||
**/
|
||||
|
||||
public abstract boolean doFormatDetect(final byte[] data, String extension);
|
||||
|
||||
/**
|
||||
*
|
||||
* This method is invoked when the file has been loaded (and doFormatDetect() has
|
||||
* already returned true.)
|
||||
*
|
||||
**/
|
||||
|
||||
public abstract void fileLoaded(byte[] data, String extension);
|
||||
|
||||
/**
|
||||
*
|
||||
* This method is invoked when the file is about to be saved.
|
||||
*
|
||||
**/
|
||||
|
||||
public abstract void fileSaving(byte[] data, String extension);
|
||||
|
||||
}
|
72
src/tm/fileselection/TMApprovedFileOpenChooser.java
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.fileselection;
|
||||
|
||||
import java.io.File;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
/**
|
||||
*
|
||||
* A filechooser that verifies that the selected file exists and
|
||||
* that it is readable before it lets the user proceed.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMApprovedFileOpenChooser extends JFileChooser {
|
||||
|
||||
public void approveSelection() {
|
||||
File file = getSelectedFile();
|
||||
// verify that it has proper extension
|
||||
FileFilter ff = getFileFilter();
|
||||
if (!ff.accept(file)) {
|
||||
String absPath = file.getAbsolutePath();
|
||||
if (!absPath.endsWith(".")) absPath += ".";
|
||||
String[] exts = ((TMFileFilter)ff).getExtensions();
|
||||
File tempFile=null;
|
||||
for (int i=0; i<exts.length; i++) {
|
||||
String tempPath = absPath + exts[i];
|
||||
tempFile = new File(tempPath);
|
||||
if (tempFile.exists()) {
|
||||
setSelectedFile(tempFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// verify that file exists and can be read
|
||||
file = getSelectedFile();
|
||||
if (!file.exists()) {
|
||||
JOptionPane.showMessageDialog(getParent(),
|
||||
"The file '"+file.getName()+"' does not exist.", // i18n
|
||||
"",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else if (!file.canRead()) {
|
||||
JOptionPane.showMessageDialog(getParent(),
|
||||
"The file '"+file.getName()+"' cannot be read.\nIt may be in use by another application.", // i18n
|
||||
"",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else {
|
||||
super.approveSelection();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
79
src/tm/fileselection/TMApprovedFileSaveChooser.java
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.fileselection;
|
||||
|
||||
import java.io.File;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
/**
|
||||
*
|
||||
* A filechooser that verifies that the selected file doesn't exist;
|
||||
* if it does, the user is asked whether he wants to overwrite it
|
||||
* before he can proceed.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMApprovedFileSaveChooser extends JFileChooser {
|
||||
|
||||
public void approveSelection() {
|
||||
File file = getSelectedFile();
|
||||
// verify that it has proper extension
|
||||
FileFilter ff = getFileFilter();
|
||||
if (!ff.accept(file)) {
|
||||
String absPath = file.getAbsolutePath();
|
||||
if (!absPath.endsWith(".")) absPath += ".";
|
||||
setSelectedFile(new File(absPath+((TMFileFilter)ff).getDefaultExtension()));
|
||||
}
|
||||
file = getSelectedFile();
|
||||
if (file.exists()) {
|
||||
int retVal = JOptionPane.showConfirmDialog(getParent(),
|
||||
"The file '"+file.getName()+"' already exists.\nDo you wish to overwrite it?", // i18n
|
||||
"",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
if (retVal == JOptionPane.NO_OPTION)
|
||||
return;
|
||||
// delete and recreate the file
|
||||
file.delete();
|
||||
try {
|
||||
file.createNewFile();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
file.createNewFile();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (!file.canWrite()) {
|
||||
JOptionPane.showMessageDialog(getParent(),
|
||||
"The file '"+file.getName()+"' cannot be written to.", // i18n
|
||||
"",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
super.approveSelection();
|
||||
}
|
||||
}
|
169
src/tm/fileselection/TMFileFilter.java
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.fileselection;
|
||||
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
import java.io.File;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
*
|
||||
* A file filter that allows you to register a set of extensions with a common
|
||||
* description.
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMFileFilter extends FileFilter {
|
||||
|
||||
private String description;
|
||||
private String[] extensions;
|
||||
private String extlist;
|
||||
|
||||
public TMFileFilter() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param extlist A comma-separated list of extensions
|
||||
*
|
||||
**/
|
||||
|
||||
public TMFileFilter(String extlist, String description) {
|
||||
super();
|
||||
setExtensions(extlist);
|
||||
setDescription(description);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public void setExtensions(String extlist) {
|
||||
this.extlist = extlist;
|
||||
StringTokenizer st = new StringTokenizer(extlist, ",");
|
||||
extensions = new String[st.countTokens()];
|
||||
for (int i=0; i<extensions.length; i++) {
|
||||
extensions[i] = st.nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public String[] getExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public String getExtlist() {
|
||||
return extlist;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public String getDefaultExtension() {
|
||||
return extensions[0];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public boolean accept(File f) {
|
||||
if (f.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
String ext = getExtension(f);
|
||||
for (int i=0; i<extensions.length; i++) {
|
||||
String suppExt = extensions[i];
|
||||
if (suppExt.length() != ext.length()) {
|
||||
continue; // can't possibly match if length different
|
||||
}
|
||||
boolean match = true;
|
||||
for (int j=0; j<ext.length(); j++) {
|
||||
if (suppExt.charAt(j) == '?') {
|
||||
continue; // '?' matches any character
|
||||
}
|
||||
if (suppExt.charAt(j) != ext.charAt(j)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the file extension.
|
||||
*
|
||||
**/
|
||||
|
||||
public static String getExtension(File f) {
|
||||
String ext = "";
|
||||
String s = f.getName();
|
||||
int i = s.lastIndexOf('.');
|
||||
|
||||
if (i > 0 && i < s.length() - 1) {
|
||||
ext = s.substring(i+1).toLowerCase();
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
}
|
88
src/tm/fileselection/TMPaletteFileFilter.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.fileselection;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public class TMPaletteFileFilter extends TMFileFilter {
|
||||
|
||||
private String codecID; // color codec
|
||||
private int size;
|
||||
private int offset;
|
||||
private int endianness;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public TMPaletteFileFilter(String extlist, String description, String codecID, int size, int offset, int endianness) {
|
||||
super(extlist, description);
|
||||
this.codecID = codecID;
|
||||
this.size = size;
|
||||
this.offset = offset;
|
||||
this.endianness = endianness;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public String getCodecID() {
|
||||
return codecID;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public int getEndianness() {
|
||||
return endianness;
|
||||
}
|
||||
|
||||
}
|
78
src/tm/fileselection/TMTileCodecFileFilter.java
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2003 Kent Hansen.
|
||||
*
|
||||
* This file is part of Tile Molester.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
* Tile Molester 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package tm.fileselection;
|
||||
|
||||
public class TMTileCodecFileFilter extends TMFileFilter {
|
||||
|
||||
private int defaultMode;
|
||||
private String codecID;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public TMTileCodecFileFilter(String extlist, String description, String codecID, int defaultMode) {
|
||||
super(extlist, description);
|
||||
setCodecID(codecID);
|
||||
setDefaultMode(defaultMode);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public void setCodecID(String codecID) {
|
||||
this.codecID = codecID;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public void setDefaultMode(int defaultMode) {
|
||||
this.defaultMode = defaultMode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public int getDefaultMode() {
|
||||
return defaultMode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
public String getCodecID() {
|
||||
return codecID;
|
||||
}
|
||||
|
||||
}
|
543
src/tm/gfxlibs/BMPLoader.java
Normal file
|
@ -0,0 +1,543 @@
|
|||
//
|
||||
|
||||
// This code was taken and cleaned up from a
|
||||
|
||||
// Javaworld tips and tricks column
|
||||
|
||||
//
|
||||
|
||||
package tm.gfxlibs;
|
||||
|
||||
import java.awt.image.*;
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
//
|
||||
|
||||
// really just a collection of methods to read a BMP file
|
||||
|
||||
//
|
||||
|
||||
public class BMPLoader
|
||||
|
||||
{
|
||||
|
||||
|
||||
// build an int from a byte array - convert little to big endian
|
||||
|
||||
public static int constructInt(byte[] in,int offset) {
|
||||
int ret = ((int)in[offset + 3] & 0xff);
|
||||
ret = (ret << 8) | ((int)in[offset + 2] & 0xff);
|
||||
ret = (ret << 8) | ((int)in[offset + 1] & 0xff);
|
||||
ret = (ret << 8) | ((int)in[offset + 0] & 0xff);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
// build an int from a byte array - convert little to big endian
|
||||
|
||||
// set high order bytes to 0xfff
|
||||
|
||||
public static int constructInt3(byte[] in,int offset) {
|
||||
int ret = 0xff;
|
||||
ret = (ret << 8) | ((int)in[offset + 2] & 0xff);
|
||||
ret = (ret << 8) | ((int)in[offset + 1] & 0xff);
|
||||
ret = (ret << 8) | ((int)in[offset + 0] & 0xff);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// build an int from a byte array - convert little to big endian
|
||||
|
||||
public static long constructLong(byte[] in,int offset) {
|
||||
long ret = ((long)in[offset + 7] & 0xff);
|
||||
ret |= (ret << 8) | ((long)in[offset + 6] & 0xff);
|
||||
ret |= (ret << 8) | ((long)in[offset + 5] & 0xff);
|
||||
ret |= (ret << 8) | ((long)in[offset + 4] & 0xff);
|
||||
ret |= (ret << 8) | ((long)in[offset + 3] & 0xff);
|
||||
ret |= (ret << 8) | ((long)in[offset + 2] & 0xff);
|
||||
ret |= (ret << 8) | ((long)in[offset + 1] & 0xff);
|
||||
ret |= (ret << 8) | ((long)in[offset + 0] & 0xff);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// build an double from a byte array - convert little to big endian
|
||||
|
||||
public static double constructDouble(byte[] in,int offset) {
|
||||
long ret = constructLong(in,offset);
|
||||
return(Double.longBitsToDouble(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// build an short from a byte array - convert little to big endian
|
||||
|
||||
public static short constructShort(byte[] in,int offset) {
|
||||
short ret = (short)(in[(offset + 1)] & 0xFF);
|
||||
ret = (short)(ret << 8 | (short)(in[(offset + 0)] & 0xFF));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
|
||||
// internal class representing a bitmap header structure
|
||||
|
||||
// with code to read it from a file
|
||||
|
||||
static class BitmapHeader {
|
||||
public int nsize;
|
||||
public int nbisize;
|
||||
public int nwidth;
|
||||
public int nheight;
|
||||
public int nplanes;
|
||||
public int nbitcount;
|
||||
public int ncompression;
|
||||
public int nsizeimage;
|
||||
public int nxpm;
|
||||
public int nypm;
|
||||
public int nclrused;
|
||||
public int nclrimp;
|
||||
|
||||
|
||||
// read in the bitmap header
|
||||
|
||||
public void read(FileInputStream fs) throws IOException
|
||||
|
||||
{
|
||||
final int bflen=14; // 14 byte BITMAPFILEHEADER
|
||||
|
||||
byte bf[]=new byte[bflen];
|
||||
fs.read(bf,0,bflen);
|
||||
final int bilen=40; // 40-byte BITMAPINFOHEADER
|
||||
|
||||
byte bi[]=new byte[bilen];
|
||||
fs.read(bi,0,bilen);
|
||||
|
||||
|
||||
// Interperet data.
|
||||
|
||||
nsize = constructInt(bf,2);
|
||||
// System.out.println("File type is :"+(char)bf[0]+(char)bf[1]);
|
||||
// System.out.println("Size of file is :"+nsize);
|
||||
|
||||
|
||||
nbisize = constructInt(bi,2);
|
||||
// System.out.println("Size of bitmapinfoheader is :"+nbisize);
|
||||
|
||||
|
||||
nwidth = constructInt(bi,4);
|
||||
// System.out.println("Width is :"+nwidth);
|
||||
|
||||
|
||||
nheight = constructInt(bi,8);
|
||||
// System.out.println("Height is :"+nheight);
|
||||
|
||||
|
||||
nplanes = constructShort(bi,12); //(((int)bi[13]&0xff)<<8) | (int)bi[12]&0xff;
|
||||
// System.out.println("Planes is :"+nplanes);
|
||||
|
||||
|
||||
nbitcount = constructShort(bi,14); //(((int)bi[15]&0xff)<<8) | (int)bi[14]&0xff;
|
||||
// System.out.println("BitCount is :"+nbitcount);
|
||||
|
||||
|
||||
// Look for non-zero values to indicate compression
|
||||
|
||||
ncompression = constructInt(bi,16);
|
||||
// System.out.println("Compression is :"+ncompression);
|
||||
|
||||
|
||||
nsizeimage = constructInt(bi,20);
|
||||
// System.out.println("SizeImage is :"+nsizeimage);
|
||||
|
||||
|
||||
nxpm = constructInt(bi,24);
|
||||
// System.out.println("X-Pixels per meter is :"+nxpm);
|
||||
|
||||
|
||||
nypm = constructInt(bi,28);
|
||||
// System.out.println("Y-Pixels per meter is :"+nypm);
|
||||
|
||||
|
||||
nclrused = constructInt(bi,32);
|
||||
// System.out.println("Colors used are :"+nclrused);
|
||||
|
||||
|
||||
nclrimp = constructInt(bi,36);
|
||||
// System.out.println("Colors important are :"+nclrimp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Image read(FileInputStream fs)
|
||||
|
||||
{
|
||||
try {
|
||||
BitmapHeader bh = new BitmapHeader();
|
||||
bh.read(fs);
|
||||
|
||||
|
||||
if (bh.nbitcount==24)
|
||||
|
||||
return(readMap24(fs,bh));
|
||||
|
||||
|
||||
if (bh.nbitcount==32)
|
||||
|
||||
return(readMap32(fs,bh));
|
||||
|
||||
|
||||
if (bh.nbitcount==8)
|
||||
|
||||
return(readMap8(fs,bh));
|
||||
|
||||
|
||||
fs.close();
|
||||
}
|
||||
|
||||
catch (IOException e)
|
||||
|
||||
{
|
||||
// System.out.println("Caught exception in loadbitmap!");
|
||||
}
|
||||
|
||||
return(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
readMap24 internal routine to read the bytes in a 24 bit bitmap
|
||||
|
||||
|
||||
|
||||
Arguments:
|
||||
|
||||
fs - file stream
|
||||
|
||||
bh - header struct
|
||||
|
||||
Returns:
|
||||
|
||||
Image Object, be sure to check for (Image)null !!!!
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
protected static Image readMap32(FileInputStream fs,BitmapHeader bh) throws IOException
|
||||
|
||||
{
|
||||
Image image;
|
||||
// No Palatte data for 24-bit format but scan lines are
|
||||
|
||||
// padded out to even 4-byte boundaries.
|
||||
|
||||
int xwidth = bh.nsizeimage / bh.nheight;
|
||||
int ndata[] = new int [bh.nheight * bh.nwidth];
|
||||
byte brgb[] = new byte [ bh.nwidth * 4 * bh.nheight];
|
||||
fs.read (brgb, 0, bh.nwidth * 4 * bh.nheight);
|
||||
int nindex = 0;
|
||||
for (int j = 0; j < bh.nheight; j++)
|
||||
|
||||
{
|
||||
for (int i = 0; i < bh.nwidth; i++)
|
||||
|
||||
{
|
||||
ndata [bh.nwidth * (bh.nheight - j - 1) + i] = constructInt3(brgb,nindex);
|
||||
nindex += 4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
image = Toolkit.getDefaultToolkit().createImage
|
||||
|
||||
( new MemoryImageSource(bh.nwidth, bh.nheight,
|
||||
|
||||
ndata, 0, bh.nwidth));
|
||||
fs.close();
|
||||
return(image);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
readMap24 internal routine to read the bytes in a 24 bit bitmap
|
||||
|
||||
|
||||
|
||||
Arguments:
|
||||
|
||||
fs - file stream
|
||||
|
||||
bh - header struct
|
||||
|
||||
Returns:
|
||||
|
||||
Image Object, be sure to check for (Image)null !!!!
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
protected static Image readMap24(FileInputStream fs,BitmapHeader bh) throws IOException
|
||||
|
||||
{
|
||||
Image image;
|
||||
// No Palatte data for 24-bit format but scan lines are
|
||||
|
||||
// padded out to even 4-byte boundaries.
|
||||
|
||||
int npad = (bh.nsizeimage / bh.nheight) - bh.nwidth * 3;
|
||||
int ndata[] = new int [bh.nheight * bh.nwidth];
|
||||
byte brgb[] = new byte [( bh.nwidth + npad) * 3 * bh.nheight];
|
||||
fs.read (brgb, 0, (bh.nwidth + npad) * 3 * bh.nheight);
|
||||
int nindex = 0;
|
||||
for (int j = 0; j < bh.nheight; j++)
|
||||
|
||||
{
|
||||
for (int i = 0; i < bh.nwidth; i++)
|
||||
|
||||
{
|
||||
ndata [bh.nwidth * (bh.nheight - j - 1) + i] = constructInt3(brgb,nindex);
|
||||
nindex += 3;
|
||||
}
|
||||
|
||||
nindex += npad;
|
||||
}
|
||||
|
||||
|
||||
|
||||
image = Toolkit.getDefaultToolkit().createImage
|
||||
|
||||
( new MemoryImageSource(bh.nwidth, bh.nheight,
|
||||
|
||||
ndata, 0, bh.nwidth));
|
||||
fs.close();
|
||||
return(image);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
readMap8 internal routine to read the bytes in a 8 bit bitmap
|
||||
|
||||
|
||||
|
||||
Arguments:
|
||||
|
||||
fs - file stream
|
||||
|
||||
bh - header struct
|
||||
|
||||
Returns:
|
||||
|
||||
Image Object, be sure to check for (Image)null !!!!
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
protected static Image readMap8(FileInputStream fs,BitmapHeader bh) throws IOException
|
||||
|
||||
{
|
||||
Image image;
|
||||
|
||||
|
||||
// Have to determine the number of colors, the clrsused
|
||||
|
||||
// parameter is dominant if it is greater than zero. If
|
||||
|
||||
// zero, calculate colors based on bitsperpixel.
|
||||
|
||||
int nNumColors = 0;
|
||||
if (bh.nclrused > 0)
|
||||
|
||||
{
|
||||
nNumColors = bh.nclrused;
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
{
|
||||
nNumColors = (1&0xff)<< bh.nbitcount;
|
||||
}
|
||||
|
||||
// System.out.println("The number of Colors is"+nNumColors);
|
||||
|
||||
|
||||
// Some bitmaps do not have the sizeimage field calculated
|
||||
|
||||
// Ferret out these cases and fix 'em.
|
||||
|
||||
if (bh.nsizeimage == 0)
|
||||
|
||||
{
|
||||
bh.nsizeimage = ((((bh.nwidth* bh.nbitcount)+31) & ~31 ) >> 3);
|
||||
bh.nsizeimage *= bh.nheight;
|
||||
// System.out.println("nsizeimage (backup) is"+nsizeimage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Read the palatte colors.
|
||||
|
||||
int npalette[] = new int [nNumColors];
|
||||
byte bpalette[] = new byte [nNumColors*4];
|
||||
fs.read (bpalette, 0, nNumColors*4);
|
||||
int nindex8 = 0;
|
||||
for (int n = 0; n < nNumColors; n++)
|
||||
|
||||
{
|
||||
npalette[n] = constructInt3(bpalette,nindex8);
|
||||
nindex8 += 4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Read the image data (actually indices into the palette)
|
||||
|
||||
// Scan lines are still padded out to even 4-byte
|
||||
|
||||
// boundaries.
|
||||
|
||||
int npad8 = (bh.nsizeimage / bh.nheight) - bh.nwidth;
|
||||
// System.out.println("nPad is:"+npad8);
|
||||
|
||||
|
||||
int ndata8[] = new int [bh.nwidth * bh.nheight];
|
||||
byte bdata[] = new byte [(bh.nwidth+npad8)* bh.nheight];
|
||||
fs.read (bdata, 0, (bh.nwidth+npad8)*bh.nheight);
|
||||
nindex8 = 0;
|
||||
for (int j8 = 0; j8 < bh.nheight; j8++)
|
||||
{
|
||||
for (int i8 = 0; i8 < bh.nwidth; i8++)
|
||||
{
|
||||
ndata8[(bh.nwidth * (bh.nheight - j8 - 1) + i8)] =
|
||||
|
||||
npalette[(bdata[nindex8] & 0xFF)];
|
||||
nindex8++;
|
||||
}
|
||||
nindex8 += npad8;
|
||||
}
|
||||
|
||||
|
||||
|
||||
image = Toolkit.getDefaultToolkit().createImage
|
||||
|
||||
( new MemoryImageSource (bh.nwidth, bh.nheight,
|
||||
|
||||
ndata8, 0, bh.nwidth));
|
||||
|
||||
|
||||
return(image);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
load method - see read for details
|
||||
|
||||
|
||||
|
||||
Arguments:
|
||||
|
||||
sdir and sfile are the result of the FileDialog()
|
||||
|
||||
getDirectory() and getFile() methods.
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
Image Object, be sure to check for (Image)null !!!!
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
public static Image load(String sdir, String sfile) {
|
||||
return(load(sdir + sfile));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
load method - see read for details
|
||||
|
||||
|
||||
|
||||
Arguments:
|
||||
|
||||
sdir - full path name
|
||||
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
Image Object, be sure to check for (Image)null !!!!
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
public static Image load(String sdir)
|
||||
|
||||
{
|
||||
try
|
||||
|
||||
{
|
||||
FileInputStream fs=new FileInputStream(sdir);
|
||||
return(read(fs));
|
||||
}
|
||||
|
||||
catch(IOException ex) {
|
||||
return(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void main(String[] args) throws IOException
|
||||
|
||||
{
|
||||
if(args.length == 0){
|
||||
System.out.println("Usage >java BMPLoader ImageFile.bmp");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
FileInputStream in = new FileInputStream(args[0]);
|
||||
Image TheImage = read(in);
|
||||
JFrame TheFrame = new JFrame(args[0]);
|
||||
JLabel TheLabel = new JLabel(new ImageIcon(TheImage));
|
||||
TheFrame.getContentPane().add(new JScrollPane(TheLabel));
|
||||
TheFrame.setSize(300,300);
|
||||
TheFrame.setVisible(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// end class BMPLoader
|
||||
|
||||
}
|
||||
|
305
src/tm/gfxlibs/BMPReader.java
Normal file
|
@ -0,0 +1,305 @@
|
|||
package tm.gfxlibs;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
|
||||
// This class provides a public static method that takes an InputStream
|
||||
// to a Windows .BMP file and converts it into an ImageProducer via
|
||||
// a MemoryImageSource.
|
||||
// You can fetch a .BMP through a URL with the following code:
|
||||
// URL url = new URL( <wherever your URL is> )
|
||||
// Image img = createImage(BMPReader.getBMPImage(url.openStream()));
|
||||
|
||||
public class BMPReader extends Object
|
||||
{
|
||||
// Constants indicating how the data is stored
|
||||
public static final int BI_RGB = 0;
|
||||
public static final int BI_RLE8 = 1;
|
||||
public static final int BI_RLE4 = 2;
|
||||
|
||||
public static ImageProducer getBMPImage(InputStream stream)
|
||||
throws IOException
|
||||
{
|
||||
// The DataInputStream allows you to read in 16 and 32 bit numbers
|
||||
DataInputStream in = new DataInputStream(stream);
|
||||
|
||||
// Verify that the header starts with 'BM'
|
||||
|
||||
if (in.read() != 'B') {
|
||||
throw new IOException("Not a .BMP file");
|
||||
}
|
||||
if (in.read() != 'M') {
|
||||
throw new IOException("Not a .BMP file");
|
||||
}
|
||||
|
||||
// Get the total file size
|
||||
int fileSize = intelInt(in.readInt());
|
||||
|
||||
// Skip the 2 16-bit reserved words
|
||||
in.readUnsignedShort();
|
||||
in.readUnsignedShort();
|
||||
|
||||
int bitmapOffset = intelInt(in.readInt());
|
||||
|
||||
int bitmapInfoSize = intelInt(in.readInt());
|
||||
|
||||
int width = intelInt(in.readInt());
|
||||
int height = intelInt(in.readInt());
|
||||
|
||||
// Skip the 16-bit bitplane size
|
||||
in.readUnsignedShort();
|
||||
|
||||
int bitCount = intelShort(in.readUnsignedShort());
|
||||
|
||||
int compressionType = intelInt(in.readInt());
|
||||
|
||||
int imageSize = intelInt(in.readInt());
|
||||
|
||||
// Skip pixels per meter
|
||||
in.readInt();
|
||||
in.readInt();
|
||||
|
||||
int colorsUsed = intelInt(in.readInt());
|
||||
int colorsImportant = intelInt(in.readInt());
|
||||
if (colorsUsed == 0) colorsUsed = 1 << bitCount;
|
||||
|
||||
int colorTable[] = new int[colorsUsed];
|
||||
|
||||
// Read the bitmap's color table
|
||||
for (int i=0; i < colorsUsed; i++) {
|
||||
colorTable[i] = (intelInt(in.readInt()) & 0xffffff) + 0xff000000;
|
||||
}
|
||||
|
||||
// Create space for the pixels
|
||||
int pixels[] = new int[width * height];
|
||||
|
||||
// Read the pixels from the stream based on the compression type
|
||||
if (compressionType == BI_RGB) {
|
||||
if (bitCount == 24) {
|
||||
readRGB24(width, height, pixels, in);
|
||||
} else {
|
||||
readRGB(width, height, colorTable, bitCount,
|
||||
pixels, in);
|
||||
}
|
||||
} else if (compressionType == BI_RLE8) {
|
||||
readRLE(width, height, colorTable, bitCount,
|
||||
pixels, in, imageSize, 8);
|
||||
} else if (compressionType == BI_RLE4) {
|
||||
readRLE(width, height, colorTable, bitCount,
|
||||
pixels, in, imageSize, 4);
|
||||
}
|
||||
|
||||
// Create a memory image source from the pixels
|
||||
return new MemoryImageSource(width, height, pixels, 0,
|
||||
width);
|
||||
}
|
||||
|
||||
// Reads in pixels in 24-bit format. There is no color table, and the
|
||||
// pixels are stored in 3-byte pairs. Oddly, all windows bitmaps are
|
||||
// stored upside-down - the bottom line is stored first.
|
||||
|
||||
protected static void readRGB24(int width, int height, int pixels[],
|
||||
DataInputStream in)
|
||||
throws IOException
|
||||
{
|
||||
|
||||
// Start storing at the bottom of the array
|
||||
for (int h = height-1; h >= 0; h--) {
|
||||
int pos = h * width;
|
||||
for (int w = 0; w < width; w++) {
|
||||
|
||||
// Read in the red, green, and blue components
|
||||
int red = in.read();
|
||||
int green = in.read();
|
||||
int blue = in.read();
|
||||
|
||||
// Turn the red, green, and blue values into an RGB color with
|
||||
// an alpha value of 255 (fully opaque)
|
||||
pixels[pos++] = 0xff000000 + (red << 16) +
|
||||
(green << 8) + blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readRGB reads in pixels values that are stored uncompressed.
|
||||
// The bits represent indices into the color table.
|
||||
|
||||
protected static void readRGB(int width, int height, int colorTable[],
|
||||
int bitCount, int pixels[], DataInputStream in)
|
||||
throws IOException
|
||||
{
|
||||
|
||||
// How many pixels can be stored in a byte?
|
||||
int pixelsPerByte = 8 / bitCount;
|
||||
|
||||
// A bit mask containing the number of bits in a pixel
|
||||
int bitMask = (1 << bitCount) - 1;
|
||||
|
||||
// The shift values that will move each pixel to the far right
|
||||
int bitShifts[] = new int[pixelsPerByte];
|
||||
|
||||
for (int i=0; i < pixelsPerByte; i++) {
|
||||
bitShifts[i] = 8 - ((i+1) * bitCount);
|
||||
}
|
||||
|
||||
int whichBit = 0;
|
||||
|
||||
// Read in the first byte
|
||||
int currByte = in.read();
|
||||
|
||||
// Start at the bottom of the pixel array and work up
|
||||
for (int h=height-1; h >= 0; h--) {
|
||||
int pos = h * width;
|
||||
for (int w=0; w < width; w++) {
|
||||
|
||||
// Get the next pixel from the current byte
|
||||
pixels[pos] = colorTable[
|
||||
(currByte >> bitShifts[whichBit]) &
|
||||
bitMask];
|
||||
pos++;
|
||||
whichBit++;
|
||||
|
||||
// If the current bit position is past the number of pixels in
|
||||
// a byte, we advance to the next byte
|
||||
if (whichBit >= pixelsPerByte) {
|
||||
whichBit = 0;
|
||||
currByte = in.read();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// readRLE reads run-length encoded data in either RLE4 or RLE8 format.
|
||||
|
||||
protected static void readRLE(int width, int height, int colorTable[],
|
||||
int bitCount, int pixels[], DataInputStream in,
|
||||
int imageSize, int pixelSize)
|
||||
throws IOException
|
||||
{
|
||||
int x = 0;
|
||||
int y = height-1;
|
||||
|
||||
// You already know how many bytes are in the image, so only go
|
||||
// through that many.
|
||||
|
||||
for (int i=0; i < imageSize; i++) {
|
||||
|
||||
// RLE encoding is defined by two bytes
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
i += 2;
|
||||
|
||||
// If byte 0 == 0, this is an escape code
|
||||
if (byte1 == 0) {
|
||||
|
||||
// If escaped, byte 2 == 0 means you are at end of line
|
||||
if (byte2 == 0) {
|
||||
x = 0;
|
||||
y--;
|
||||
|
||||
// If escaped, byte 2 == 1 means end of bitmap
|
||||
} else if (byte2 == 1) {
|
||||
return;
|
||||
|
||||
// if escaped, byte 2 == 2 adjusts the current x and y by
|
||||
// an offset stored in the next two words
|
||||
} else if (byte2 == 2) {
|
||||
int xoff = (char) intelShort(
|
||||
in.readUnsignedShort());
|
||||
i+= 2;
|
||||
int yoff = (char) intelShort(
|
||||
in.readUnsignedShort());
|
||||
i+= 2;
|
||||
x += xoff;
|
||||
y -= yoff;
|
||||
|
||||
// If escaped, any other value for byte 2 is the number of bytes
|
||||
// that you should read as pixel values (these pixels are not
|
||||
// run-length encoded)
|
||||
} else {
|
||||
int whichBit = 0;
|
||||
|
||||
// Read in the next byte
|
||||
int currByte = in.read();
|
||||
|
||||
i++;
|
||||
for (int j=0; j < byte2; j++) {
|
||||
|
||||
if (pixelSize == 4) {
|
||||
// The pixels are 4-bits, so half the time you shift the current byte
|
||||
// to the right as the pixel value
|
||||
if (whichBit == 0) {
|
||||
pixels[y*width+x] = colorTable[(currByte >> 4)
|
||||
& 0xf];
|
||||
} else {
|
||||
|
||||
// The rest of the time, you mask out the upper 4 bits, save the pixel
|
||||
// value, then read in the next byte
|
||||
|
||||
pixels[y*width+x] = colorTable[currByte & 0xf];
|
||||
currByte = in.read();
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
pixels[y*width+x] = colorTable[currByte];
|
||||
currByte = in.read();
|
||||
i++;
|
||||
}
|
||||
x++;
|
||||
if (x >= width) {
|
||||
x = 0;
|
||||
y--;
|
||||
}
|
||||
}
|
||||
// The pixels must be word-aligned, so if you read an uneven number of
|
||||
// bytes, read and ignore a byte to get aligned again.
|
||||
if ((byte2 & 1) == 1) {
|
||||
in.read();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If the first byte was not 0, it is the number of pixels that
|
||||
// are encoded by byte 2
|
||||
} else {
|
||||
for (int j=0; j < byte1; j++) {
|
||||
|
||||
if (pixelSize == 4) {
|
||||
// If j is odd, use the upper 4 bits
|
||||
if ((j & 1) == 0) {
|
||||
pixels[y*width+x] = colorTable[(byte2 >> 4) & 0xf];
|
||||
} else {
|
||||
pixels[y*width+x+1] = colorTable[byte2 & 0xf];
|
||||
}
|
||||
} else {
|
||||
pixels[y*width+x+1] = colorTable[byte2];
|
||||
}
|
||||
x++;
|
||||
if (x >= width) {
|
||||
x = 0;
|
||||
y--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// intelShort converts a 16-bit number stored in intel byte order into
|
||||
// the local host format
|
||||
|
||||
protected static int intelShort(int i)
|
||||
{
|
||||
return ((i >> 8) & 0xff) + ((i << 8) & 0xff00);
|
||||
}
|
||||
|
||||
// intelInt converts a 32-bit number stored in intel byte order into
|
||||
// the local host format
|
||||
|
||||
protected static int intelInt(int i)
|
||||
{
|
||||
return ((i & 0xff) << 24) + ((i & 0xff00) << 8) +
|
||||
((i & 0xff0000) >> 8) + ((i >> 24) & 0xff);
|
||||
}
|
||||
}
|
1010
src/tm/gfxlibs/GIFOutputStream.java
Normal file
271
src/tm/gfxlibs/ImageEncoder.java
Normal file
|
@ -0,0 +1,271 @@
|
|||
// ImageEncoder - abstract class for writing out an image
|
||||
//
|
||||
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
//
|
||||
// Visit the ACME Labs Java page for up-to-date versions of this and other
|
||||
// fine Java utilities: http://www.acme.com/java/
|
||||
|
||||
package tm.gfxlibs;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.*;
|
||||
|
||||
/// Abstract class for writing out an image.
|
||||
// <P>
|
||||
// A framework for classes that encode and write out an image in
|
||||
// a particular file format.
|
||||
// <P>
|
||||
// This provides a simplified rendition of the ImageConsumer interface.
|
||||
// It always delivers the pixels as ints in the RGBdefault color model.
|
||||
// It always provides them in top-down left-right order.
|
||||
// If you want more flexibility you can always implement ImageConsumer
|
||||
// directly.
|
||||
// <P>
|
||||
// <A HREF="/resources/classes/Acme/JPM/Encoders/ImageEncoder.java">Fetch the software.</A><BR>
|
||||
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
|
||||
// <P>
|
||||
// @see GifEncoder
|
||||
// @see PpmEncoder
|
||||
// @see Acme.JPM.Decoders.ImageDecoder
|
||||
|
||||
public abstract class ImageEncoder implements ImageConsumer
|
||||
{
|
||||
|
||||
protected OutputStream out;
|
||||
|
||||
private ImageProducer producer;
|
||||
private int width = -1;
|
||||
private int height = -1;
|
||||
private int hintflags = 0;
|
||||
private boolean started = false;
|
||||
private boolean encoding;
|
||||
private IOException iox;
|
||||
private static final ColorModel rgbModel = ColorModel.getRGBdefault();
|
||||
private Hashtable props = null;
|
||||
|
||||
/// Constructor.
|
||||
// @param img The image to encode.
|
||||
// @param out The stream to write the bytes to.
|
||||
public ImageEncoder( Image img, OutputStream out ) throws IOException
|
||||
{
|
||||
this( img.getSource(), out );
|
||||
}
|
||||
|
||||
/// Constructor.
|
||||
// @param producer The ImageProducer to encode.
|
||||
// @param out The stream to write the bytes to.
|
||||
public ImageEncoder( ImageProducer producer, OutputStream out ) throws IOException
|
||||
{
|
||||
this.producer = producer;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
|
||||
// Methods that subclasses implement.
|
||||
|
||||
/// Subclasses implement this to initialize an encoding.
|
||||
abstract void encodeStart( int w, int h ) throws IOException;
|
||||
|
||||
/// Subclasses implement this to actually write out some bits. They
|
||||
// are guaranteed to be delivered in top-down-left-right order.
|
||||
// One int per pixel, index is row * scansize + off + col,
|
||||
// RGBdefault (AARRGGBB) color model.
|
||||
abstract void encodePixels(
|
||||
int x, int y, int w, int h, int[] rgbPixels, int off, int scansize )
|
||||
throws IOException;
|
||||
|
||||
/// Subclasses implement this to finish an encoding.
|
||||
abstract void encodeDone() throws IOException;
|
||||
|
||||
|
||||
// Our own methods.
|
||||
|
||||
/// Call this after initialization to get things going.
|
||||
public synchronized void encode() throws IOException
|
||||
{
|
||||
encoding = true;
|
||||
iox = null;
|
||||
producer.startProduction( this );
|
||||
while ( encoding )
|
||||
try
|
||||
{
|
||||
wait();
|
||||
}
|
||||
catch ( InterruptedException e ) {}
|
||||
if ( iox != null )
|
||||
throw iox;
|
||||
}
|
||||
|
||||
private boolean accumulate = false;
|
||||
private int[] accumulator;
|
||||
|
||||
private void encodePixelsWrapper(
|
||||
int x, int y, int w, int h, int[] rgbPixels, int off, int scansize )
|
||||
throws IOException
|
||||
{
|
||||
if ( ! started )
|
||||
{
|
||||
started = true;
|
||||
encodeStart( width, height );
|
||||
if ( ( hintflags & TOPDOWNLEFTRIGHT ) == 0 )
|
||||
{
|
||||
accumulate = true;
|
||||
accumulator = new int[width * height];
|
||||
}
|
||||
}
|
||||
if ( accumulate )
|
||||
for ( int row = 0; row < h; ++row )
|
||||
System.arraycopy(
|
||||
rgbPixels, row * scansize + off,
|
||||
accumulator, ( y + row ) * width + x,
|
||||
w );
|
||||
else
|
||||
encodePixels( x, y, w, h, rgbPixels, off, scansize );
|
||||
}
|
||||
|
||||
private void encodeFinish() throws IOException
|
||||
{
|
||||
if ( accumulate )
|
||||
{
|
||||
encodePixels( 0, 0, width, height, accumulator, 0, width );
|
||||
accumulator = null;
|
||||
accumulate = false;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void stop()
|
||||
{
|
||||
encoding = false;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
|
||||
// Methods from ImageConsumer.
|
||||
|
||||
public void setDimensions( int width, int height )
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void setProperties( Hashtable props )
|
||||
{
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
public void setColorModel( ColorModel model )
|
||||
{
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
public void setHints( int hintflags )
|
||||
{
|
||||
this.hintflags = hintflags;
|
||||
}
|
||||
|
||||
public void setPixels(
|
||||
int x, int y, int w, int h, ColorModel model, byte[] pixels,
|
||||
int off, int scansize )
|
||||
{
|
||||
int[] rgbPixels = new int[w];
|
||||
for ( int row = 0; row < h; ++row )
|
||||
{
|
||||
int rowOff = off + row * scansize;
|
||||
for ( int col = 0; col < w; ++col )
|
||||
rgbPixels[col] = model.getRGB( pixels[rowOff + col] & 0xff );
|
||||
try
|
||||
{
|
||||
encodePixelsWrapper( x, y + row, w, 1, rgbPixels, 0, w );
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
iox = e;
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPixels(
|
||||
int x, int y, int w, int h, ColorModel model, int[] pixels,
|
||||
int off, int scansize )
|
||||
{
|
||||
if ( model == rgbModel )
|
||||
{
|
||||
try
|
||||
{
|
||||
encodePixelsWrapper( x, y, w, h, pixels, off, scansize );
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
iox = e;
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int[] rgbPixels = new int[w];
|
||||
for ( int row = 0; row < h; ++row )
|
||||
{
|
||||
int rowOff = off + row * scansize;
|
||||
for ( int col = 0; col < w; ++col )
|
||||
rgbPixels[col] = model.getRGB( pixels[rowOff + col] );
|
||||
try
|
||||
{
|
||||
encodePixelsWrapper( x, y + row, w, 1, rgbPixels, 0, w );
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
iox = e;
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void imageComplete( int status )
|
||||
{
|
||||
producer.removeConsumer( this );
|
||||
if ( status == ImageConsumer.IMAGEABORTED )
|
||||
iox = new IOException( "image aborted" );
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
encodeFinish();
|
||||
encodeDone();
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
iox = e;
|
||||
}
|
||||
}
|
||||
stop();
|
||||
}
|
||||
|
||||
}
|
147
src/tm/gfxlibs/PCXEncoder.java
Normal file
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
*
|
||||
* @author Kent Hansen
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
**/
|
||||
|
||||
package tm.gfxlibs;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.image.PixelGrabber;
|
||||
import java.awt.image.ImageObserver;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
public class PCXEncoder {
|
||||
|
||||
private static ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
public static byte[] encode(Image image) {
|
||||
// get width and height of image
|
||||
int w = image.getWidth(null);
|
||||
int h = image.getHeight(null);
|
||||
|
||||
// grab the pixels
|
||||
int[] pixels = new int[w * h];
|
||||
PixelGrabber pg = new PixelGrabber(image, 0, 0, w, h, pixels, 0, w);
|
||||
try {
|
||||
pg.grabPixels();
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("interrupted waiting for pixels!");
|
||||
return null;
|
||||
}
|
||||
if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
|
||||
System.err.println("image fetch aborted or errored");
|
||||
return null;
|
||||
}
|
||||
|
||||
// prepare the byte stream
|
||||
baos.reset();
|
||||
|
||||
// write the header
|
||||
emitByte(10); // Manufacturer = ZSoft
|
||||
emitByte(5); // Version = 3.0+ (supports 24-bit PCX)
|
||||
emitByte(1); // Encoding = RLE
|
||||
emitByte(8); // BitsPerPixel (per plane)
|
||||
emitInt(0); // Xmin
|
||||
emitInt(0); // Ymin
|
||||
emitInt(w-1); // Xmax
|
||||
emitInt(h-1); // Ymax
|
||||
emitInt(0); // HDpi (not used)
|
||||
emitInt(0); // VDpi (not used)
|
||||
emitZeroes(48); // Colormap (not used)
|
||||
emitZeroes(1); // Reserved
|
||||
emitByte(3); // NPlanes
|
||||
emitInt(w); // BytesPerLine
|
||||
emitInt(0); // PaletteInfo (not used)
|
||||
emitInt(w); // HScreenSize (unsure what this is for)
|
||||
emitInt(h); // VScreenSize (unsure what this is for)
|
||||
emitZeroes(54); // Filler
|
||||
|
||||
// encode the pixels
|
||||
int ofs=0;
|
||||
int[] plane = new int[w];
|
||||
for (int i=0; i<h; i++) {
|
||||
// red plane
|
||||
for (int j=0; j<w; j++) {
|
||||
plane[j] = getRed(pixels[ofs+j]);
|
||||
}
|
||||
encodePlane(plane);
|
||||
// green plane
|
||||
for (int j=0; j<w; j++) {
|
||||
plane[j] = getGreen(pixels[ofs+j]);
|
||||
}
|
||||
encodePlane(plane);
|
||||
// blue plane
|
||||
for (int j=0; j<w; j++) {
|
||||
plane[j] = getBlue(pixels[ofs+j]);
|
||||
}
|
||||
encodePlane(plane);
|
||||
ofs += w;
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static void emitByte(int b) {
|
||||
baos.write(b);
|
||||
}
|
||||
|
||||
private static void emitInt(int i) {
|
||||
baos.write(i & 0xFF);
|
||||
baos.write((i >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
private static void emitZeroes(int c) {
|
||||
for (int i=0; i<c; i++) {
|
||||
emitByte(0x00);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getRed(int rgb) {
|
||||
return (rgb >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
private static int getGreen(int rgb) {
|
||||
return (rgb >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
private static int getBlue(int rgb) {
|
||||
return rgb & 0xFF;
|
||||
}
|
||||
|
||||
private static void encodePlane(int[] plane) {
|
||||
int i=0;
|
||||
while (i < plane.length) {
|
||||
int v = plane[i];
|
||||
if ((i != plane.length-1) && (v == plane[i+1])) {
|
||||
// run-length encode
|
||||
int c = 1;
|
||||
while ((c < 0x40) && (i+c < plane.length) && (v == plane[i+c])) {
|
||||
c++;
|
||||
}
|
||||
i += c;
|
||||
emitByte(c | 0xC0);
|
||||
emitByte(v);
|
||||
}
|
||||
else {
|
||||
// regular encode
|
||||
if ((v & 0xC0) == 0xC0) {
|
||||
emitByte(0xC1);
|
||||
}
|
||||
emitByte(v);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
236
src/tm/gfxlibs/PCXReader.java
Normal file
|
@ -0,0 +1,236 @@
|
|||
/* PcxReader: This is a class which provides a method for reading
|
||||
* PCX-Files.
|
||||
* The PCX-Format is a Image-File-Format which was developed by ZSoft.
|
||||
*
|
||||
* PcxReader, Version 1.00 [06/05/2000]
|
||||
* Copyright (c) 2000 by Matthias Burg
|
||||
* All rights reserved
|
||||
* eMail: Matthias@burgsoft.de
|
||||
* Internet: www.burgsoft.de
|
||||
*
|
||||
* The PcxReader is Freeware. You can use and copy it without any fee.
|
||||
* The author is not responsible for any damages which are caused by
|
||||
* this software.
|
||||
* You find further information about the Java-Technology on the Sun-
|
||||
* Website (Internet: www.sun.com).
|
||||
*
|
||||
* At the moment PcxReader supports the following File-Formats:
|
||||
* - PCX Version 3.0 with 8 Bit (=256) Colors
|
||||
* - PCX Version 3.0 with 24 Bit (=16.7 Mio) Colors
|
||||
*
|
||||
* The PcxReader needs an opened InputStream with the PCX-Data as
|
||||
* Argument. The return-value is the loaded Image.
|
||||
* You can use the PcxReader in a Java-Application as well as in a
|
||||
* Java-Applet
|
||||
*
|
||||
* If you have questions or tips for the PcxReader, please write an
|
||||
* eMail.
|
||||
*/
|
||||
|
||||
package tm.gfxlibs;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
public class PCXReader
|
||||
{
|
||||
/* This is the main-class of the PcxReader. It reads the PCX-Data
|
||||
* from a stream and converts it into an Image-Class.
|
||||
*/
|
||||
|
||||
public static final int NORMAL = 1;
|
||||
public static final int RLE = 2;
|
||||
|
||||
public static Image loadImage(InputStream in)
|
||||
{
|
||||
int pcxheight, pcxwidth;
|
||||
Image picture = null;
|
||||
|
||||
//Header-Data
|
||||
int manufacturer;
|
||||
int version;
|
||||
int encoding;
|
||||
int bits_per_pixel;
|
||||
int xmin,ymin;
|
||||
int xmax,ymax;
|
||||
int hres;
|
||||
int vres;
|
||||
byte[] palette16 = new byte[48];
|
||||
int reserved;
|
||||
int color_planes;
|
||||
int bytes_per_line;
|
||||
int palette_type;
|
||||
byte[] filler = new byte[58];
|
||||
|
||||
int imagebytes;
|
||||
try
|
||||
{
|
||||
/* In the beginning the Image-Data is read as in the PCX-
|
||||
* specification.
|
||||
*/
|
||||
manufacturer = in.read();
|
||||
version = in.read();
|
||||
encoding = in.read();
|
||||
bits_per_pixel = in.read();
|
||||
|
||||
xmin = in.read()+in.read()*256;
|
||||
ymin = in.read()+in.read()*256;
|
||||
xmax = in.read()+in.read()*256;
|
||||
ymax = in.read()+in.read()*256;
|
||||
hres = in.read()+in.read()*256;
|
||||
vres = in.read()+in.read()*256;
|
||||
in.read(palette16);
|
||||
reserved = in.read();
|
||||
color_planes = in.read();
|
||||
bytes_per_line = in.read()+in.read()*256;
|
||||
palette_type = (short)(in.read()+in.read()*256);
|
||||
in.read(filler);
|
||||
|
||||
|
||||
pcxwidth = 1+xmax-xmin;
|
||||
pcxheight = 1+ymax-ymin;
|
||||
if(pcxwidth % 2 == 1)
|
||||
{
|
||||
/* The width of an PCX-Image must be even. That is why the
|
||||
* width is increased when it was odd before.
|
||||
*/
|
||||
pcxwidth++;
|
||||
}
|
||||
|
||||
|
||||
if(bits_per_pixel == 8 && color_planes == 1)
|
||||
{
|
||||
/* If the PCX-file has 256 colors there is a color-palete
|
||||
* at the end of the file. This is 768b bytes long and
|
||||
* contains the red- green- and blue-values of the colors.
|
||||
*/
|
||||
byte[] pal = new byte[768];
|
||||
int[] intPal = new int[768];
|
||||
|
||||
imagebytes = (pcxwidth*pcxheight);
|
||||
int[] imageData = new int[imagebytes];
|
||||
readRLECompressedData(imagebytes, imageData, in);
|
||||
|
||||
if(in.available() > 769)
|
||||
{
|
||||
while(in.available() > 769)
|
||||
{
|
||||
in.read();
|
||||
}
|
||||
}
|
||||
|
||||
if(in.available() != 769)
|
||||
{
|
||||
System.out.println("Error in the palette!");
|
||||
}
|
||||
if(in.read()!=12)
|
||||
{
|
||||
System.out.println("Error in the palette!");
|
||||
}
|
||||
|
||||
in.read(pal);
|
||||
in.close();
|
||||
for(int y = 0; y <767;y++)
|
||||
{
|
||||
intPal[y] = (int)(pal[y]);
|
||||
if(intPal[y] < 0)
|
||||
{
|
||||
intPal[y] += 256;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now the PcxReader converts the imagedata into the format
|
||||
* of a MemoryImageSource.
|
||||
*/
|
||||
|
||||
int RGBImageData[] = new int[imagebytes];
|
||||
for(int i = 0; i < imagebytes; i++)
|
||||
{
|
||||
int paletteEntry = imageData[i];
|
||||
if(paletteEntry < 0) paletteEntry += 256;
|
||||
RGBImageData[i] = new Color(intPal[paletteEntry*3],
|
||||
intPal[paletteEntry*3+1],
|
||||
intPal[paletteEntry*3+2]).getRGB();
|
||||
}
|
||||
|
||||
ImageProducer prod = new MemoryImageSource(pcxwidth, pcxheight, RGBImageData, 0, pcxwidth);
|
||||
picture = Toolkit.getDefaultToolkit().createImage(prod);
|
||||
|
||||
}
|
||||
else if(bits_per_pixel == 8 && color_planes == 3)
|
||||
{
|
||||
/* If the picture has 24 bit colors, there are 3 times many
|
||||
* bytes as many pixels.
|
||||
*/
|
||||
|
||||
imagebytes = (pcxwidth*pcxheight*3);
|
||||
int[] imageData = new int[imagebytes];
|
||||
readRLECompressedData(imagebytes, imageData, in);
|
||||
|
||||
|
||||
int RGBImageData[] = new int[imagebytes];
|
||||
for(int i = 0; i < pcxheight; i++)
|
||||
{
|
||||
for(int j = 0; j < pcxwidth; j++)
|
||||
{
|
||||
int red = imageData[i*3*pcxwidth+j];
|
||||
int green = imageData[((i*3)+1)*pcxwidth+j];
|
||||
int blue = imageData[((i*3)+2)*pcxwidth+j];
|
||||
RGBImageData[i*pcxwidth+j] = new Color(red,green,blue).getRGB();
|
||||
}
|
||||
}
|
||||
|
||||
ImageProducer prod = new MemoryImageSource(pcxwidth, pcxheight, RGBImageData, 0, pcxwidth);
|
||||
picture = Toolkit.getDefaultToolkit().createImage(prod);
|
||||
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
System.out.println("Error reading PCX-File!");
|
||||
}
|
||||
|
||||
return picture;
|
||||
}
|
||||
|
||||
private static void readRLECompressedData(int imagebytes, int[] imageData, InputStream in)
|
||||
throws IOException
|
||||
{
|
||||
/* This method reads the compressed data-stream and decompresses
|
||||
* it in the memory.
|
||||
*/
|
||||
|
||||
int i;
|
||||
int mode=NORMAL,nbytes=0;
|
||||
int abyte = 0;
|
||||
|
||||
for(i = 0; i<imagebytes;i++)
|
||||
{
|
||||
if (mode == NORMAL)
|
||||
{
|
||||
abyte = in.read();
|
||||
if(abyte > 191)
|
||||
{
|
||||
nbytes=abyte-192;
|
||||
abyte =(byte)(in.read());
|
||||
if (--nbytes > 0)
|
||||
{
|
||||
mode = RLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(--nbytes == 0)
|
||||
{
|
||||
mode = NORMAL;
|
||||
}
|
||||
|
||||
imageData[i] = abyte;
|
||||
if(imageData[i] < 0) imageData[i] += 256;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
621
src/tm/gfxlibs/PngEncoder.java
Normal file
|
@ -0,0 +1,621 @@
|
|||
/**
|
||||
* PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file.
|
||||
* The Image is presumed to use the DirectColorModel.
|
||||
*
|
||||
* Thanks to Jay Denny at KeyPoint Software
|
||||
* http://www.keypoint.com/
|
||||
* who let me develop this code on company time.
|
||||
*
|
||||
* You may contact me with (probably very-much-needed) improvements,
|
||||
* comments, and bug fixes at:
|
||||
*
|
||||
* david@catcode.com
|
||||
*
|
||||
* 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 2.1 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; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
* A copy of the GNU LGPL may be found at
|
||||
* http://www.gnu.org/copyleft/lesser.html,
|
||||
*
|
||||
* @author J. David Eisenberg
|
||||
* @version 1.4, 31 March 2000
|
||||
*/
|
||||
|
||||
package tm.gfxlibs;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.*;
|
||||
import java.io.*;
|
||||
|
||||
public class PngEncoder extends Object
|
||||
{
|
||||
/** Constant specifying that alpha channel should be encoded. */
|
||||
public static final boolean ENCODE_ALPHA=true;
|
||||
/** Constant specifying that alpha channel should not be encoded. */
|
||||
public static final boolean NO_ALPHA=false;
|
||||
/** Constants for filters */
|
||||
public static final int FILTER_NONE = 0;
|
||||
public static final int FILTER_SUB = 1;
|
||||
public static final int FILTER_UP = 2;
|
||||
public static final int FILTER_LAST = 2;
|
||||
|
||||
protected byte[] pngBytes;
|
||||
protected byte[] priorRow;
|
||||
protected byte[] leftBytes;
|
||||
protected Image image;
|
||||
protected int width, height;
|
||||
protected int bytePos, maxPos;
|
||||
protected int hdrPos, dataPos, endPos;
|
||||
protected CRC32 crc = new CRC32();
|
||||
protected long crcValue;
|
||||
protected boolean encodeAlpha;
|
||||
protected int filter;
|
||||
protected int bytesPerPixel;
|
||||
protected int compressionLevel;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
*/
|
||||
public PngEncoder()
|
||||
{
|
||||
this( null, false, FILTER_NONE, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor specifying Image to encode, with no alpha channel encoding.
|
||||
*
|
||||
* @param image A Java Image object which uses the DirectColorModel
|
||||
* @see java.awt.Image
|
||||
*/
|
||||
public PngEncoder( Image image )
|
||||
{
|
||||
this(image, false, FILTER_NONE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor specifying Image to encode, and whether to encode alpha.
|
||||
*
|
||||
* @param image A Java Image object which uses the DirectColorModel
|
||||
* @param encodeAlpha Encode the alpha channel? false=no; true=yes
|
||||
* @see java.awt.Image
|
||||
*/
|
||||
public PngEncoder( Image image, boolean encodeAlpha )
|
||||
{
|
||||
this(image, encodeAlpha, FILTER_NONE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor specifying Image to encode, whether to encode alpha, and filter to use.
|
||||
*
|
||||
* @param image A Java Image object which uses the DirectColorModel
|
||||
* @param encodeAlpha Encode the alpha channel? false=no; true=yes
|
||||
* @param whichFilter 0=none, 1=sub, 2=up
|
||||
* @see java.awt.Image
|
||||
*/
|
||||
public PngEncoder( Image image, boolean encodeAlpha, int whichFilter )
|
||||
{
|
||||
this( image, encodeAlpha, whichFilter, 0 );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor specifying Image source to encode, whether to encode alpha, filter to use, and compression level.
|
||||
*
|
||||
* @param image A Java Image object
|
||||
* @param encodeAlpha Encode the alpha channel? false=no; true=yes
|
||||
* @param whichFilter 0=none, 1=sub, 2=up
|
||||
* @param compLevel 0..9
|
||||
* @see java.awt.Image
|
||||
*/
|
||||
public PngEncoder( Image image, boolean encodeAlpha, int whichFilter,
|
||||
int compLevel)
|
||||
{
|
||||
this.image = image;
|
||||
this.encodeAlpha = encodeAlpha;
|
||||
setFilter( whichFilter );
|
||||
if (compLevel >=0 && compLevel <=9)
|
||||
{
|
||||
this.compressionLevel = compLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the image to be encoded
|
||||
*
|
||||
* @param image A Java Image object which uses the DirectColorModel
|
||||
* @see java.awt.Image
|
||||
* @see java.awt.image.DirectColorModel
|
||||
*/
|
||||
public void setImage( Image image )
|
||||
{
|
||||
this.image = image;
|
||||
pngBytes = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of bytes that is the PNG equivalent of the current image, specifying whether to encode alpha or not.
|
||||
*
|
||||
* @param encodeAlpha boolean false=no alpha, true=encode alpha
|
||||
* @return an array of bytes, or null if there was a problem
|
||||
*/
|
||||
public byte[] pngEncode( boolean encodeAlpha )
|
||||
{
|
||||
byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 };
|
||||
int i;
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
width = image.getWidth( null );
|
||||
height = image.getHeight( null );
|
||||
this.image = image;
|
||||
|
||||
/*
|
||||
* start with an array that is big enough to hold all the pixels
|
||||
* (plus filter bytes), and an extra 200 bytes for header info
|
||||
*/
|
||||
pngBytes = new byte[((width+1) * height * 3) + 200];
|
||||
|
||||
/*
|
||||
* keep track of largest byte written to the array
|
||||
*/
|
||||
maxPos = 0;
|
||||
|
||||
bytePos = writeBytes( pngIdBytes, 0 );
|
||||
hdrPos = bytePos;
|
||||
writeHeader();
|
||||
dataPos = bytePos;
|
||||
if (writeImageData())
|
||||
{
|
||||
writeEnd();
|
||||
pngBytes = resizeByteArray( pngBytes, maxPos );
|
||||
}
|
||||
else
|
||||
{
|
||||
pngBytes = null;
|
||||
}
|
||||
return pngBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of bytes that is the PNG equivalent of the current image.
|
||||
* Alpha encoding is determined by its setting in the constructor.
|
||||
*
|
||||
* @return an array of bytes, or null if there was a problem
|
||||
*/
|
||||
public byte[] pngEncode()
|
||||
{
|
||||
return pngEncode( encodeAlpha );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alpha encoding on or off.
|
||||
*
|
||||
* @param encodeAlpha false=no, true=yes
|
||||
*/
|
||||
public void setEncodeAlpha( boolean encodeAlpha )
|
||||
{
|
||||
this.encodeAlpha = encodeAlpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve alpha encoding status.
|
||||
*
|
||||
* @return boolean false=no, true=yes
|
||||
*/
|
||||
public boolean getEncodeAlpha()
|
||||
{
|
||||
return encodeAlpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filter to use
|
||||
*
|
||||
* @param whichFilter from constant list
|
||||
*/
|
||||
public void setFilter( int whichFilter )
|
||||
{
|
||||
this.filter = FILTER_NONE;
|
||||
if ( whichFilter <= FILTER_LAST )
|
||||
{
|
||||
this.filter = whichFilter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve filtering scheme
|
||||
*
|
||||
* @return int (see constant list)
|
||||
*/
|
||||
public int getFilter()
|
||||
{
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the compression level to use
|
||||
*
|
||||
* @param level 0 through 9
|
||||
*/
|
||||
public void setCompressionLevel( int level )
|
||||
{
|
||||
if ( level >= 0 && level <= 9)
|
||||
{
|
||||
this.compressionLevel = level;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve compression level
|
||||
*
|
||||
* @return int in range 0-9
|
||||
*/
|
||||
public int getCompressionLevel()
|
||||
{
|
||||
return compressionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase or decrease the length of a byte array.
|
||||
*
|
||||
* @param array The original array.
|
||||
* @param newLength The length you wish the new array to have.
|
||||
* @return Array of newly desired length. If shorter than the
|
||||
* original, the trailing elements are truncated.
|
||||
*/
|
||||
protected byte[] resizeByteArray( byte[] array, int newLength )
|
||||
{
|
||||
byte[] newArray = new byte[newLength];
|
||||
int oldLength = array.length;
|
||||
|
||||
System.arraycopy( array, 0, newArray, 0,
|
||||
Math.min( oldLength, newLength ) );
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an array of bytes into the pngBytes array.
|
||||
* Note: This routine has the side effect of updating
|
||||
* maxPos, the largest element written in the array.
|
||||
* The array is resized by 1000 bytes or the length
|
||||
* of the data to be written, whichever is larger.
|
||||
*
|
||||
* @param data The data to be written into pngBytes.
|
||||
* @param offset The starting point to write to.
|
||||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
protected int writeBytes( byte[] data, int offset )
|
||||
{
|
||||
maxPos = Math.max( maxPos, offset + data.length );
|
||||
if (data.length + offset > pngBytes.length)
|
||||
{
|
||||
pngBytes = resizeByteArray( pngBytes, pngBytes.length +
|
||||
Math.max( 1000, data.length ) );
|
||||
}
|
||||
System.arraycopy( data, 0, pngBytes, offset, data.length );
|
||||
return offset + data.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an array of bytes into the pngBytes array, specifying number of bytes to write.
|
||||
* Note: This routine has the side effect of updating
|
||||
* maxPos, the largest element written in the array.
|
||||
* The array is resized by 1000 bytes or the length
|
||||
* of the data to be written, whichever is larger.
|
||||
*
|
||||
* @param data The data to be written into pngBytes.
|
||||
* @param nBytes The number of bytes to be written.
|
||||
* @param offset The starting point to write to.
|
||||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
protected int writeBytes( byte[] data, int nBytes, int offset )
|
||||
{
|
||||
maxPos = Math.max( maxPos, offset + nBytes );
|
||||
if (nBytes + offset > pngBytes.length)
|
||||
{
|
||||
pngBytes = resizeByteArray( pngBytes, pngBytes.length +
|
||||
Math.max( 1000, nBytes ) );
|
||||
}
|
||||
System.arraycopy( data, 0, pngBytes, offset, nBytes );
|
||||
return offset + nBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a two-byte integer into the pngBytes array at a given position.
|
||||
*
|
||||
* @param n The integer to be written into pngBytes.
|
||||
* @param offset The starting point to write to.
|
||||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
protected int writeInt2( int n, int offset )
|
||||
{
|
||||
byte[] temp = { (byte)((n >> 8) & 0xff),
|
||||
(byte) (n & 0xff) };
|
||||
return writeBytes( temp, offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a four-byte integer into the pngBytes array at a given position.
|
||||
*
|
||||
* @param n The integer to be written into pngBytes.
|
||||
* @param offset The starting point to write to.
|
||||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
protected int writeInt4( int n, int offset )
|
||||
{
|
||||
byte[] temp = { (byte)((n >> 24) & 0xff),
|
||||
(byte) ((n >> 16) & 0xff ),
|
||||
(byte) ((n >> 8) & 0xff ),
|
||||
(byte) ( n & 0xff ) };
|
||||
return writeBytes( temp, offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a single byte into the pngBytes array at a given position.
|
||||
*
|
||||
* @param n The integer to be written into pngBytes.
|
||||
* @param offset The starting point to write to.
|
||||
* @return The next place to be written to in the pngBytes array.
|
||||
*/
|
||||
protected int writeByte( int b, int offset )
|
||||
{
|
||||
byte[] temp = { (byte) b };
|
||||
return writeBytes( temp, offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string into the pngBytes array at a given position.
|
||||
* This uses the getBytes method, so the encoding used will
|
||||
* be its default.
|
||||
*
|
||||
* @param n The integer to be written into pngBytes.
|
||||
* @param offset The starting point to write to.
|
||||
* @return The next place to be written to in the pngBytes array.
|
||||
* @see java.lang.String#getBytes()
|
||||
*/
|
||||
protected int writeString( String s, int offset )
|
||||
{
|
||||
return writeBytes( s.getBytes(), offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a PNG "IHDR" chunk into the pngBytes array.
|
||||
*/
|
||||
protected void writeHeader()
|
||||
{
|
||||
int startPos;
|
||||
|
||||
startPos = bytePos = writeInt4( 13, bytePos );
|
||||
bytePos = writeString( "IHDR", bytePos );
|
||||
width = image.getWidth( null );
|
||||
height = image.getHeight( null );
|
||||
bytePos = writeInt4( width, bytePos );
|
||||
bytePos = writeInt4( height, bytePos );
|
||||
bytePos = writeByte( 8, bytePos ); // bit depth
|
||||
bytePos = writeByte( (encodeAlpha) ? 6 : 2, bytePos ); // direct model
|
||||
bytePos = writeByte( 0, bytePos ); // compression method
|
||||
bytePos = writeByte( 0, bytePos ); // filter method
|
||||
bytePos = writeByte( 0, bytePos ); // no interlace
|
||||
crc.reset();
|
||||
crc.update( pngBytes, startPos, bytePos-startPos );
|
||||
crcValue = crc.getValue();
|
||||
bytePos = writeInt4( (int) crcValue, bytePos );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform "sub" filtering on the given row.
|
||||
* Uses temporary array leftBytes to store the original values
|
||||
* of the previous pixels. The array is 16 bytes long, which
|
||||
* will easily hold two-byte samples plus two-byte alpha.
|
||||
*
|
||||
* @param pixels The array holding the scan lines being built
|
||||
* @param startPos Starting position within pixels of bytes to be filtered.
|
||||
* @param width Width of a scanline in pixels.
|
||||
*/
|
||||
protected void filterSub( byte[] pixels, int startPos, int width )
|
||||
{
|
||||
int i;
|
||||
int offset = bytesPerPixel;
|
||||
int actualStart = startPos + offset;
|
||||
int nBytes = width * bytesPerPixel;
|
||||
int leftInsert = offset;
|
||||
int leftExtract = 0;
|
||||
byte current_byte;
|
||||
|
||||
for (i=actualStart; i < startPos + nBytes; i++)
|
||||
{
|
||||
leftBytes[leftInsert] = pixels[i];
|
||||
pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256);
|
||||
leftInsert = (leftInsert+1) % 0x0f;
|
||||
leftExtract = (leftExtract + 1) % 0x0f;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform "up" filtering on the given row.
|
||||
* Side effect: refills the prior row with current row
|
||||
*
|
||||
* @param pixels The array holding the scan lines being built
|
||||
* @param startPos Starting position within pixels of bytes to be filtered.
|
||||
* @param width Width of a scanline in pixels.
|
||||
*/
|
||||
protected void filterUp( byte[] pixels, int startPos, int width )
|
||||
{
|
||||
int i, nBytes;
|
||||
byte current_byte;
|
||||
|
||||
nBytes = width * bytesPerPixel;
|
||||
|
||||
for (i=0; i < nBytes; i++)
|
||||
{
|
||||
current_byte = pixels[startPos + i];
|
||||
pixels[startPos + i] = (byte) ((pixels[startPos + i] - priorRow[i]) % 256);
|
||||
priorRow[i] = current_byte;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the image data into the pngBytes array.
|
||||
* This will write one or more PNG "IDAT" chunks. In order
|
||||
* to conserve memory, this method grabs as many rows as will
|
||||
* fit into 32K bytes, or the whole image; whichever is less.
|
||||
*
|
||||
*
|
||||
* @return true if no errors; false if error grabbing pixels
|
||||
*/
|
||||
protected boolean writeImageData()
|
||||
{
|
||||
int rowsLeft = height; // number of rows remaining to write
|
||||
int startRow = 0; // starting row to process this time through
|
||||
int nRows; // how many rows to grab at a time
|
||||
|
||||
byte[] scanLines; // the scan lines to be compressed
|
||||
int scanPos; // where we are in the scan lines
|
||||
int startPos; // where this line's actual pixels start (used for filtering)
|
||||
|
||||
byte[] compressedLines; // the resultant compressed lines
|
||||
int nCompressed; // how big is the compressed area?
|
||||
|
||||
int depth; // color depth ( handle only 8 or 32 )
|
||||
|
||||
PixelGrabber pg;
|
||||
|
||||
bytesPerPixel = (encodeAlpha) ? 4 : 3;
|
||||
|
||||
Deflater scrunch = new Deflater( compressionLevel );
|
||||
ByteArrayOutputStream outBytes =
|
||||
new ByteArrayOutputStream(1024);
|
||||
|
||||
DeflaterOutputStream compBytes =
|
||||
new DeflaterOutputStream( outBytes, scrunch );
|
||||
try
|
||||
{
|
||||
while (rowsLeft > 0)
|
||||
{
|
||||
nRows = Math.min( 32767 / (width*(bytesPerPixel+1)), rowsLeft );
|
||||
// nRows = rowsLeft;
|
||||
|
||||
int[] pixels = new int[width * nRows];
|
||||
|
||||
pg = new PixelGrabber(image, 0, startRow,
|
||||
width, nRows, pixels, 0, width);
|
||||
try {
|
||||
pg.grabPixels();
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.err.println("interrupted waiting for pixels!");
|
||||
return false;
|
||||
}
|
||||
if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
|
||||
System.err.println("image fetch aborted or errored");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a data chunk. scanLines adds "nRows" for
|
||||
* the filter bytes.
|
||||
*/
|
||||
scanLines = new byte[width * nRows * bytesPerPixel + nRows];
|
||||
|
||||
if (filter == FILTER_SUB)
|
||||
{
|
||||
leftBytes = new byte[16];
|
||||
}
|
||||
if (filter == FILTER_UP)
|
||||
{
|
||||
priorRow = new byte[width*bytesPerPixel];
|
||||
}
|
||||
|
||||
scanPos = 0;
|
||||
startPos = 1;
|
||||
for (int i=0; i<width*nRows; i++)
|
||||
{
|
||||
if (i % width == 0)
|
||||
{
|
||||
scanLines[scanPos++] = (byte) filter;
|
||||
startPos = scanPos;
|
||||
}
|
||||
scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff);
|
||||
scanLines[scanPos++] = (byte) ((pixels[i] >> 8) & 0xff);
|
||||
scanLines[scanPos++] = (byte) ((pixels[i] ) & 0xff);
|
||||
if (encodeAlpha)
|
||||
{
|
||||
scanLines[scanPos++] = (byte) ((pixels[i] >> 24) & 0xff );
|
||||
}
|
||||
if ((i % width == width-1) && (filter != FILTER_NONE))
|
||||
{
|
||||
if (filter == FILTER_SUB)
|
||||
{
|
||||
filterSub( scanLines, startPos, width );
|
||||
}
|
||||
if (filter == FILTER_UP)
|
||||
{
|
||||
filterUp( scanLines, startPos, width );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write these lines to the output area
|
||||
*/
|
||||
compBytes.write( scanLines, 0, scanPos );
|
||||
|
||||
|
||||
startRow += nRows;
|
||||
rowsLeft -= nRows;
|
||||
}
|
||||
compBytes.close();
|
||||
|
||||
/*
|
||||
* Write the compressed bytes
|
||||
*/
|
||||
compressedLines = outBytes.toByteArray();
|
||||
nCompressed = compressedLines.length;
|
||||
|
||||
crc.reset();
|
||||
bytePos = writeInt4( nCompressed, bytePos );
|
||||
bytePos = writeString("IDAT", bytePos );
|
||||
crc.update("IDAT".getBytes());
|
||||
bytePos = writeBytes( compressedLines, nCompressed, bytePos );
|
||||
crc.update( compressedLines, 0, nCompressed );
|
||||
|
||||
crcValue = crc.getValue();
|
||||
bytePos = writeInt4( (int) crcValue, bytePos );
|
||||
scrunch.finish();
|
||||
return true;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
System.err.println( e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a PNG "IEND" chunk into the pngBytes array.
|
||||
*/
|
||||
protected void writeEnd()
|
||||
{
|
||||
bytePos = writeInt4( 0, bytePos );
|
||||
bytePos = writeString( "IEND", bytePos );
|
||||
crc.reset();
|
||||
crc.update("IEND".getBytes());
|
||||
crcValue = crc.getValue();
|
||||
bytePos = writeInt4( (int) crcValue, bytePos );
|
||||
}
|
||||
}
|
||||
|
703
src/tm/gfxlibs/Quantize.java
Normal file
|
@ -0,0 +1,703 @@
|
|||
/*
|
||||
* @(#)Quantize.java 0.90 9/19/00 Adam Doppelt
|
||||
*/
|
||||
|
||||
/**
|
||||
* An efficient color quantization algorithm, adapted from the C++
|
||||
* implementation quantize.c in <a
|
||||
* href="http://www.imagemagick.org/">ImageMagick</a>. The pixels for
|
||||
* an image are placed into an oct tree. The oct tree is reduced in
|
||||
* size, and the pixels from the original image are reassigned to the
|
||||
* nodes in the reduced tree.<p>
|
||||
*
|
||||
* Here is the copyright notice from ImageMagick:
|
||||
*
|
||||
* <pre>
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% Permission is hereby granted, free of charge, to any person obtaining a %
|
||||
% copy of this software and associated documentation files ("ImageMagick"), %
|
||||
% to deal in ImageMagick without restriction, including without limitation %
|
||||
% the rights to use, copy, modify, merge, publish, distribute, sublicense, %
|
||||
% and/or sell copies of ImageMagick, and to permit persons to whom the %
|
||||
% ImageMagick is furnished to do so, subject to the following conditions: %
|
||||
% %
|
||||
% The above copyright notice and this permission notice shall be included in %
|
||||
% all copies or substantial portions of ImageMagick. %
|
||||
% %
|
||||
% The software is provided "as is", without warranty of any kind, express or %
|
||||
% implied, including but not limited to the warranties of merchantability, %
|
||||
% fitness for a particular purpose and noninfringement. In no event shall %
|
||||
% E. I. du Pont de Nemours and Company be liable for any claim, damages or %
|
||||
% other liability, whether in an action of contract, tort or otherwise, %
|
||||
% arising from, out of or in connection with ImageMagick or the use or other %
|
||||
% dealings in ImageMagick. %
|
||||
% %
|
||||
% Except as contained in this notice, the name of the E. I. du Pont de %
|
||||
% Nemours and Company shall not be used in advertising or otherwise to %
|
||||
% promote the sale, use or other dealings in ImageMagick without prior %
|
||||
% written authorization from the E. I. du Pont de Nemours and Company. %
|
||||
% %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
</pre>
|
||||
*
|
||||
*
|
||||
* @version 0.90 19 Sep 2000
|
||||
* @author <a href="http://www.gurge.com/amd/">Adam Doppelt</a>
|
||||
*/
|
||||
|
||||
package tm.gfxlibs;
|
||||
|
||||
public class Quantize {
|
||||
|
||||
/*
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% %
|
||||
% %
|
||||
% %
|
||||
% QQQ U U AAA N N TTTTT IIIII ZZZZZ EEEEE %
|
||||
% Q Q U U A A NN N T I ZZ E %
|
||||
% Q Q U U AAAAA N N N T I ZZZ EEEEE %
|
||||
% Q QQ U U A A N NN T I ZZ E %
|
||||
% QQQQ UUU A A N N T IIIII ZZZZZ EEEEE %
|
||||
% %
|
||||
% %
|
||||
% Reduce the Number of Unique Colors in an Image %
|
||||
% %
|
||||
% %
|
||||
% Software Design %
|
||||
% John Cristy %
|
||||
% July 1992 %
|
||||
% %
|
||||
% %
|
||||
% Copyright 1998 E. I. du Pont de Nemours and Company %
|
||||
% %
|
||||
% Permission is hereby granted, free of charge, to any person obtaining a %
|
||||
% copy of this software and associated documentation files ("ImageMagick"), %
|
||||
% to deal in ImageMagick without restriction, including without limitation %
|
||||
% the rights to use, copy, modify, merge, publish, distribute, sublicense, %
|
||||
% and/or sell copies of ImageMagick, and to permit persons to whom the %
|
||||
% ImageMagick is furnished to do so, subject to the following conditions: %
|
||||
% %
|
||||
% The above copyright notice and this permission notice shall be included in %
|
||||
% all copies or substantial portions of ImageMagick. %
|
||||
% %
|
||||
% The software is provided "as is", without warranty of any kind, express or %
|
||||
% implied, including but not limited to the warranties of merchantability, %
|
||||
% fitness for a particular purpose and noninfringement. In no event shall %
|
||||
% E. I. du Pont de Nemours and Company be liable for any claim, damages or %
|
||||
% other liability, whether in an action of contract, tort or otherwise, %
|
||||
% arising from, out of or in connection with ImageMagick or the use or other %
|
||||
% dealings in ImageMagick. %
|
||||
% %
|
||||
% Except as contained in this notice, the name of the E. I. du Pont de %
|
||||
% Nemours and Company shall not be used in advertising or otherwise to %
|
||||
% promote the sale, use or other dealings in ImageMagick without prior %
|
||||
% written authorization from the E. I. du Pont de Nemours and Company. %
|
||||
% %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%
|
||||
% Realism in computer graphics typically requires using 24 bits/pixel to
|
||||
% generate an image. Yet many graphic display devices do not contain
|
||||
% the amount of memory necessary to match the spatial and color
|
||||
% resolution of the human eye. The QUANTIZE program takes a 24 bit
|
||||
% image and reduces the number of colors so it can be displayed on
|
||||
% raster device with less bits per pixel. In most instances, the
|
||||
% quantized image closely resembles the original reference image.
|
||||
%
|
||||
% A reduction of colors in an image is also desirable for image
|
||||
% transmission and real-time animation.
|
||||
%
|
||||
% Function Quantize takes a standard RGB or monochrome images and quantizes
|
||||
% them down to some fixed number of colors.
|
||||
%
|
||||
% For purposes of color allocation, an image is a set of n pixels, where
|
||||
% each pixel is a point in RGB space. RGB space is a 3-dimensional
|
||||
% vector space, and each pixel, pi, is defined by an ordered triple of
|
||||
% red, green, and blue coordinates, (ri, gi, bi).
|
||||
%
|
||||
% Each primary color component (red, green, or blue) represents an
|
||||
% intensity which varies linearly from 0 to a maximum value, cmax, which
|
||||
% corresponds to full saturation of that color. Color allocation is
|
||||
% defined over a domain consisting of the cube in RGB space with
|
||||
% opposite vertices at (0,0,0) and (cmax,cmax,cmax). QUANTIZE requires
|
||||
% cmax = 255.
|
||||
%
|
||||
% The algorithm maps this domain onto a tree in which each node
|
||||
% represents a cube within that domain. In the following discussion
|
||||
% these cubes are defined by the coordinate of two opposite vertices:
|
||||
% The vertex nearest the origin in RGB space and the vertex farthest
|
||||
% from the origin.
|
||||
%
|
||||
% The tree's root node represents the the entire domain, (0,0,0) through
|
||||
% (cmax,cmax,cmax). Each lower level in the tree is generated by
|
||||
% subdividing one node's cube into eight smaller cubes of equal size.
|
||||
% This corresponds to bisecting the parent cube with planes passing
|
||||
% through the midpoints of each edge.
|
||||
%
|
||||
% The basic algorithm operates in three phases: Classification,
|
||||
% Reduction, and Assignment. Classification builds a color
|
||||
% description tree for the image. Reduction collapses the tree until
|
||||
% the number it represents, at most, the number of colors desired in the
|
||||
% output image. Assignment defines the output image's color map and
|
||||
% sets each pixel's color by reclassification in the reduced tree.
|
||||
% Our goal is to minimize the numerical discrepancies between the original
|
||||
% colors and quantized colors (quantization error).
|
||||
%
|
||||
% Classification begins by initializing a color description tree of
|
||||
% sufficient depth to represent each possible input color in a leaf.
|
||||
% However, it is impractical to generate a fully-formed color
|
||||
% description tree in the classification phase for realistic values of
|
||||
% cmax. If colors components in the input image are quantized to k-bit
|
||||
% precision, so that cmax= 2k-1, the tree would need k levels below the
|
||||
% root node to allow representing each possible input color in a leaf.
|
||||
% This becomes prohibitive because the tree's total number of nodes is
|
||||
% 1 + sum(i=1,k,8k).
|
||||
%
|
||||
% A complete tree would require 19,173,961 nodes for k = 8, cmax = 255.
|
||||
% Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
|
||||
% Initializes data structures for nodes only as they are needed; (2)
|
||||
% Chooses a maximum depth for the tree as a function of the desired
|
||||
% number of colors in the output image (currently log2(colormap size)).
|
||||
%
|
||||
% For each pixel in the input image, classification scans downward from
|
||||
% the root of the color description tree. At each level of the tree it
|
||||
% identifies the single node which represents a cube in RGB space
|
||||
% containing the pixel's color. It updates the following data for each
|
||||
% such node:
|
||||
%
|
||||
% n1: Number of pixels whose color is contained in the RGB cube
|
||||
% which this node represents;
|
||||
%
|
||||
% n2: Number of pixels whose color is not represented in a node at
|
||||
% lower depth in the tree; initially, n2 = 0 for all nodes except
|
||||
% leaves of the tree.
|
||||
%
|
||||
% Sr, Sg, Sb: Sums of the red, green, and blue component values for
|
||||
% all pixels not classified at a lower depth. The combination of
|
||||
% these sums and n2 will ultimately characterize the mean color of a
|
||||
% set of pixels represented by this node.
|
||||
%
|
||||
% E: The distance squared in RGB space between each pixel contained
|
||||
% within a node and the nodes' center. This represents the quantization
|
||||
% error for a node.
|
||||
%
|
||||
% Reduction repeatedly prunes the tree until the number of nodes with
|
||||
% n2 > 0 is less than or equal to the maximum number of colors allowed
|
||||
% in the output image. On any given iteration over the tree, it selects
|
||||
% those nodes whose E count is minimal for pruning and merges their
|
||||
% color statistics upward. It uses a pruning threshold, Ep, to govern
|
||||
% node selection as follows:
|
||||
%
|
||||
% Ep = 0
|
||||
% while number of nodes with (n2 > 0) > required maximum number of colors
|
||||
% prune all nodes such that E <= Ep
|
||||
% Set Ep to minimum E in remaining nodes
|
||||
%
|
||||
% This has the effect of minimizing any quantization error when merging
|
||||
% two nodes together.
|
||||
%
|
||||
% When a node to be pruned has offspring, the pruning procedure invokes
|
||||
% itself recursively in order to prune the tree from the leaves upward.
|
||||
% n2, Sr, Sg, and Sb in a node being pruned are always added to the
|
||||
% corresponding data in that node's parent. This retains the pruned
|
||||
% node's color characteristics for later averaging.
|
||||
%
|
||||
% For each node, n2 pixels exist for which that node represents the
|
||||
% smallest volume in RGB space containing those pixel's colors. When n2
|
||||
% > 0 the node will uniquely define a color in the output image. At the
|
||||
% beginning of reduction, n2 = 0 for all nodes except a the leaves of
|
||||
% the tree which represent colors present in the input image.
|
||||
%
|
||||
% The other pixel count, n1, indicates the total number of colors
|
||||
% within the cubic volume which the node represents. This includes n1 -
|
||||
% n2 pixels whose colors should be defined by nodes at a lower level in
|
||||
% the tree.
|
||||
%
|
||||
% Assignment generates the output image from the pruned tree. The
|
||||
% output image consists of two parts: (1) A color map, which is an
|
||||
% array of color descriptions (RGB triples) for each color present in
|
||||
% the output image; (2) A pixel array, which represents each pixel as
|
||||
% an index into the color map array.
|
||||
%
|
||||
% First, the assignment phase makes one pass over the pruned color
|
||||
% description tree to establish the image's color map. For each node
|
||||
% with n2 > 0, it divides Sr, Sg, and Sb by n2 . This produces the
|
||||
% mean color of all pixels that classify no lower than this node. Each
|
||||
% of these colors becomes an entry in the color map.
|
||||
%
|
||||
% Finally, the assignment phase reclassifies each pixel in the pruned
|
||||
% tree to identify the deepest node containing the pixel's color. The
|
||||
% pixel's value in the pixel array becomes the index of this node's mean
|
||||
% color in the color map.
|
||||
%
|
||||
% With the permission of USC Information Sciences Institute, 4676 Admiralty
|
||||
% Way, Marina del Rey, California 90292, this code was adapted from module
|
||||
% ALCOLS written by Paul Raveling.
|
||||
%
|
||||
% The names of ISI and USC are not used in advertising or publicity
|
||||
% pertaining to distribution of the software without prior specific
|
||||
% written permission from ISI.
|
||||
%
|
||||
*/
|
||||
|
||||
final static boolean QUICK = true;
|
||||
|
||||
final static int MAX_RGB = 255;
|
||||
final static int MAX_NODES = 266817;
|
||||
final static int MAX_TREE_DEPTH = 8;
|
||||
|
||||
// these are precomputed in advance
|
||||
static int SQUARES[];
|
||||
static int SHIFT[];
|
||||
|
||||
static {
|
||||
SQUARES = new int[MAX_RGB + MAX_RGB + 1];
|
||||
for (int i= -MAX_RGB; i <= MAX_RGB; i++) {
|
||||
SQUARES[i + MAX_RGB] = i * i;
|
||||
}
|
||||
|
||||
SHIFT = new int[MAX_TREE_DEPTH + 1];
|
||||
for (int i = 0; i < MAX_TREE_DEPTH + 1; ++i) {
|
||||
SHIFT[i] = 1 << (15 - i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the image to the given number of colors. The pixels are
|
||||
* reduced in place.
|
||||
* @return The new color palette.
|
||||
*/
|
||||
public static int[] quantizeImage(int pixels[][], int max_colors) {
|
||||
Cube cube = new Cube(pixels, max_colors);
|
||||
cube.classification();
|
||||
cube.reduction();
|
||||
cube.assignment();
|
||||
return cube.colormap;
|
||||
}
|
||||
|
||||
static class Cube {
|
||||
int pixels[][];
|
||||
int max_colors;
|
||||
int colormap[];
|
||||
|
||||
Node root;
|
||||
int depth;
|
||||
|
||||
// counter for the number of colors in the cube. this gets
|
||||
// recalculated often.
|
||||
int colors;
|
||||
|
||||
// counter for the number of nodes in the tree
|
||||
int nodes;
|
||||
|
||||
Cube(int pixels[][], int max_colors) {
|
||||
this.pixels = pixels;
|
||||
this.max_colors = max_colors;
|
||||
|
||||
int i = max_colors;
|
||||
// tree_depth = log max_colors
|
||||
// 4
|
||||
for (depth = 1; i != 0; depth++) {
|
||||
i /= 4;
|
||||
}
|
||||
if (depth > 1) {
|
||||
--depth;
|
||||
}
|
||||
if (depth > MAX_TREE_DEPTH) {
|
||||
depth = MAX_TREE_DEPTH;
|
||||
} else if (depth < 2) {
|
||||
depth = 2;
|
||||
}
|
||||
|
||||
root = new Node(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Procedure Classification begins by initializing a color
|
||||
* description tree of sufficient depth to represent each
|
||||
* possible input color in a leaf. However, it is impractical
|
||||
* to generate a fully-formed color description tree in the
|
||||
* classification phase for realistic values of cmax. If
|
||||
* colors components in the input image are quantized to k-bit
|
||||
* precision, so that cmax= 2k-1, the tree would need k levels
|
||||
* below the root node to allow representing each possible
|
||||
* input color in a leaf. This becomes prohibitive because the
|
||||
* tree's total number of nodes is 1 + sum(i=1,k,8k).
|
||||
*
|
||||
* A complete tree would require 19,173,961 nodes for k = 8,
|
||||
* cmax = 255. Therefore, to avoid building a fully populated
|
||||
* tree, QUANTIZE: (1) Initializes data structures for nodes
|
||||
* only as they are needed; (2) Chooses a maximum depth for
|
||||
* the tree as a function of the desired number of colors in
|
||||
* the output image (currently log2(colormap size)).
|
||||
*
|
||||
* For each pixel in the input image, classification scans
|
||||
* downward from the root of the color description tree. At
|
||||
* each level of the tree it identifies the single node which
|
||||
* represents a cube in RGB space containing It updates the
|
||||
* following data for each such node:
|
||||
*
|
||||
* number_pixels : Number of pixels whose color is contained
|
||||
* in the RGB cube which this node represents;
|
||||
*
|
||||
* unique : Number of pixels whose color is not represented
|
||||
* in a node at lower depth in the tree; initially, n2 = 0
|
||||
* for all nodes except leaves of the tree.
|
||||
*
|
||||
* total_red/green/blue : Sums of the red, green, and blue
|
||||
* component values for all pixels not classified at a lower
|
||||
* depth. The combination of these sums and n2 will
|
||||
* ultimately characterize the mean color of a set of pixels
|
||||
* represented by this node.
|
||||
*/
|
||||
void classification() {
|
||||
int pixels[][] = this.pixels;
|
||||
|
||||
int width = pixels.length;
|
||||
int height = pixels[0].length;
|
||||
|
||||
// convert to indexed color
|
||||
for (int x = width; x-- > 0; ) {
|
||||
for (int y = height; y-- > 0; ) {
|
||||
int pixel = pixels[x][y];
|
||||
int red = (pixel >> 16) & 0xFF;
|
||||
int green = (pixel >> 8) & 0xFF;
|
||||
int blue = (pixel >> 0) & 0xFF;
|
||||
|
||||
// a hard limit on the number of nodes in the tree
|
||||
if (nodes > MAX_NODES) {
|
||||
System.out.println("pruning");
|
||||
root.pruneLevel();
|
||||
--depth;
|
||||
}
|
||||
|
||||
// walk the tree to depth, increasing the
|
||||
// number_pixels count for each node
|
||||
Node node = root;
|
||||
for (int level = 1; level <= depth; ++level) {
|
||||
int id = (((red > node.mid_red ? 1 : 0) << 0) |
|
||||
((green > node.mid_green ? 1 : 0) << 1) |
|
||||
((blue > node.mid_blue ? 1 : 0) << 2));
|
||||
if (node.child[id] == null) {
|
||||
new Node(node, id, level);
|
||||
}
|
||||
node = node.child[id];
|
||||
node.number_pixels += SHIFT[level];
|
||||
}
|
||||
|
||||
++node.unique;
|
||||
node.total_red += red;
|
||||
node.total_green += green;
|
||||
node.total_blue += blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* reduction repeatedly prunes the tree until the number of
|
||||
* nodes with unique > 0 is less than or equal to the maximum
|
||||
* number of colors allowed in the output image.
|
||||
*
|
||||
* When a node to be pruned has offspring, the pruning
|
||||
* procedure invokes itself recursively in order to prune the
|
||||
* tree from the leaves upward. The statistics of the node
|
||||
* being pruned are always added to the corresponding data in
|
||||
* that node's parent. This retains the pruned node's color
|
||||
* characteristics for later averaging.
|
||||
*/
|
||||
void reduction() {
|
||||
int threshold = 1;
|
||||
while (colors > max_colors) {
|
||||
colors = 0;
|
||||
threshold = root.reduce(threshold, Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a closest color search.
|
||||
*/
|
||||
static class Search {
|
||||
int distance;
|
||||
int color_number;
|
||||
}
|
||||
|
||||
/*
|
||||
* Procedure assignment generates the output image from the
|
||||
* pruned tree. The output image consists of two parts: (1) A
|
||||
* color map, which is an array of color descriptions (RGB
|
||||
* triples) for each color present in the output image; (2) A
|
||||
* pixel array, which represents each pixel as an index into
|
||||
* the color map array.
|
||||
*
|
||||
* First, the assignment phase makes one pass over the pruned
|
||||
* color description tree to establish the image's color map.
|
||||
* For each node with n2 > 0, it divides Sr, Sg, and Sb by n2.
|
||||
* This produces the mean color of all pixels that classify no
|
||||
* lower than this node. Each of these colors becomes an entry
|
||||
* in the color map.
|
||||
*
|
||||
* Finally, the assignment phase reclassifies each pixel in
|
||||
* the pruned tree to identify the deepest node containing the
|
||||
* pixel's color. The pixel's value in the pixel array becomes
|
||||
* the index of this node's mean color in the color map.
|
||||
*/
|
||||
void assignment() {
|
||||
colormap = new int[colors];
|
||||
|
||||
colors = 0;
|
||||
root.colormap();
|
||||
|
||||
int pixels[][] = this.pixels;
|
||||
|
||||
int width = pixels.length;
|
||||
int height = pixels[0].length;
|
||||
|
||||
Search search = new Search();
|
||||
|
||||
// convert to indexed color
|
||||
for (int x = width; x-- > 0; ) {
|
||||
for (int y = height; y-- > 0; ) {
|
||||
int pixel = pixels[x][y];
|
||||
int red = (pixel >> 16) & 0xFF;
|
||||
int green = (pixel >> 8) & 0xFF;
|
||||
int blue = (pixel >> 0) & 0xFF;
|
||||
|
||||
// walk the tree to find the cube containing that color
|
||||
Node node = root;
|
||||
for ( ; ; ) {
|
||||
int id = (((red > node.mid_red ? 1 : 0) << 0) |
|
||||
((green > node.mid_green ? 1 : 0) << 1) |
|
||||
((blue > node.mid_blue ? 1 : 0) << 2) );
|
||||
if (node.child[id] == null) {
|
||||
break;
|
||||
}
|
||||
node = node.child[id];
|
||||
}
|
||||
|
||||
if (QUICK) {
|
||||
// if QUICK is set, just use that
|
||||
// node. Strictly speaking, this isn't
|
||||
// necessarily best match.
|
||||
pixels[x][y] = node.color_number;
|
||||
} else {
|
||||
// Find the closest color.
|
||||
search.distance = Integer.MAX_VALUE;
|
||||
node.parent.closestColor(red, green, blue, search);
|
||||
pixels[x][y] = search.color_number;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single Node in the tree.
|
||||
*/
|
||||
static class Node {
|
||||
Cube cube;
|
||||
|
||||
// parent node
|
||||
Node parent;
|
||||
|
||||
// child nodes
|
||||
Node child[];
|
||||
int nchild;
|
||||
|
||||
// our index within our parent
|
||||
int id;
|
||||
// our level within the tree
|
||||
int level;
|
||||
// our color midpoint
|
||||
int mid_red;
|
||||
int mid_green;
|
||||
int mid_blue;
|
||||
|
||||
// the pixel count for this node and all children
|
||||
int number_pixels;
|
||||
|
||||
// the pixel count for this node
|
||||
int unique;
|
||||
// the sum of all pixels contained in this node
|
||||
int total_red;
|
||||
int total_green;
|
||||
int total_blue;
|
||||
|
||||
// used to build the colormap
|
||||
int color_number;
|
||||
|
||||
Node(Cube cube) {
|
||||
this.cube = cube;
|
||||
this.parent = this;
|
||||
this.child = new Node[8];
|
||||
this.id = 0;
|
||||
this.level = 0;
|
||||
|
||||
this.number_pixels = Integer.MAX_VALUE;
|
||||
|
||||
this.mid_red = (MAX_RGB + 1) >> 1;
|
||||
this.mid_green = (MAX_RGB + 1) >> 1;
|
||||
this.mid_blue = (MAX_RGB + 1) >> 1;
|
||||
}
|
||||
|
||||
Node(Node parent, int id, int level) {
|
||||
this.cube = parent.cube;
|
||||
this.parent = parent;
|
||||
this.child = new Node[8];
|
||||
this.id = id;
|
||||
this.level = level;
|
||||
|
||||
// add to the cube
|
||||
++cube.nodes;
|
||||
if (level == cube.depth) {
|
||||
++cube.colors;
|
||||
}
|
||||
|
||||
// add to the parent
|
||||
++parent.nchild;
|
||||
parent.child[id] = this;
|
||||
|
||||
// figure out our midpoint
|
||||
int bi = (1 << (MAX_TREE_DEPTH - level)) >> 1;
|
||||
mid_red = parent.mid_red + ((id & 1) > 0 ? bi : -bi);
|
||||
mid_green = parent.mid_green + ((id & 2) > 0 ? bi : -bi);
|
||||
mid_blue = parent.mid_blue + ((id & 4) > 0 ? bi : -bi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this child node, and make sure our parent
|
||||
* absorbs our pixel statistics.
|
||||
*/
|
||||
void pruneChild() {
|
||||
--parent.nchild;
|
||||
parent.unique += unique;
|
||||
parent.total_red += total_red;
|
||||
parent.total_green += total_green;
|
||||
parent.total_blue += total_blue;
|
||||
parent.child[id] = null;
|
||||
--cube.nodes;
|
||||
cube = null;
|
||||
parent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune the lowest layer of the tree.
|
||||
*/
|
||||
void pruneLevel() {
|
||||
if (nchild != 0) {
|
||||
for (int id = 0; id < 8; id++) {
|
||||
if (child[id] != null) {
|
||||
child[id].pruneLevel();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (level == cube.depth) {
|
||||
pruneChild();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any nodes that have fewer than threshold
|
||||
* pixels. Also, as long as we're walking the tree:
|
||||
*
|
||||
* - figure out the color with the fewest pixels
|
||||
* - recalculate the total number of colors in the tree
|
||||
*/
|
||||
int reduce(int threshold, int next_threshold) {
|
||||
if (nchild != 0) {
|
||||
for (int id = 0; id < 8; id++) {
|
||||
if (child[id] != null) {
|
||||
next_threshold = child[id].reduce(threshold, next_threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (number_pixels <= threshold) {
|
||||
pruneChild();
|
||||
} else {
|
||||
if (unique != 0) {
|
||||
cube.colors++;
|
||||
}
|
||||
if (number_pixels < next_threshold) {
|
||||
next_threshold = number_pixels;
|
||||
}
|
||||
}
|
||||
return next_threshold;
|
||||
}
|
||||
|
||||
/*
|
||||
* colormap traverses the color cube tree and notes each
|
||||
* colormap entry. A colormap entry is any node in the
|
||||
* color cube tree where the number of unique colors is
|
||||
* not zero.
|
||||
*/
|
||||
void colormap() {
|
||||
if (nchild != 0) {
|
||||
for (int id = 0; id < 8; id++) {
|
||||
if (child[id] != null) {
|
||||
child[id].colormap();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unique != 0) {
|
||||
int r = ((total_red + (unique >> 1)) / unique);
|
||||
int g = ((total_green + (unique >> 1)) / unique);
|
||||
int b = ((total_blue + (unique >> 1)) / unique);
|
||||
cube.colormap[cube.colors] = ((( 0xFF) << 24) |
|
||||
((r & 0xFF) << 16) |
|
||||
((g & 0xFF) << 8) |
|
||||
((b & 0xFF) << 0));
|
||||
color_number = cube.colors++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ClosestColor traverses the color cube tree at a
|
||||
* particular node and determines which colormap entry
|
||||
* best represents the input color.
|
||||
*/
|
||||
void closestColor(int red, int green, int blue, Search search) {
|
||||
if (nchild != 0) {
|
||||
for (int id = 0; id < 8; id++) {
|
||||
if (child[id] != null) {
|
||||
child[id].closestColor(red, green, blue, search);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unique != 0) {
|
||||
int color = cube.colormap[color_number];
|
||||
int distance = distance(color, red, green, blue);
|
||||
if (distance < search.distance) {
|
||||
search.distance = distance;
|
||||
search.color_number = color_number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out the distance between this node and som color.
|
||||
*/
|
||||
final static int distance(int color, int r, int g, int b) {
|
||||
return (SQUARES[((color >> 16) & 0xFF) - r + MAX_RGB] +
|
||||
SQUARES[((color >> 8) & 0xFF) - g + MAX_RGB] +
|
||||
SQUARES[((color >> 0) & 0xFF) - b + MAX_RGB]);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
if (parent == this) {
|
||||
buf.append("root");
|
||||
} else {
|
||||
buf.append("node");
|
||||
}
|
||||
buf.append(' ');
|
||||
buf.append(level);
|
||||
buf.append(" [");
|
||||
buf.append(mid_red);
|
||||
buf.append(',');
|
||||
buf.append(mid_green);
|
||||
buf.append(',');
|
||||
buf.append(mid_blue);
|
||||
buf.append(']');
|
||||
return new String(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
src/tm/icons/Add24.gif
Normal file
After Width: | Height: | Size: 470 B |
BIN
src/tm/icons/Back24.gif
Normal file
After Width: | Height: | Size: 422 B |
BIN
src/tm/icons/Bookmarks24.gif
Normal file
After Width: | Height: | Size: 283 B |
BIN
src/tm/icons/Brush24.gif
Normal file
After Width: | Height: | Size: 145 B |
BIN
src/tm/icons/BrushCursor24.gif
Normal file
After Width: | Height: | Size: 147 B |
BIN
src/tm/icons/ColorReplacer24.gif
Normal file
After Width: | Height: | Size: 166 B |
BIN
src/tm/icons/ColumnDelete24.gif
Normal file
After Width: | Height: | Size: 253 B |
BIN
src/tm/icons/ColumnInsertAfter24.gif
Normal file
After Width: | Height: | Size: 239 B |
BIN
src/tm/icons/Copy24.gif
Normal file
After Width: | Height: | Size: 682 B |
BIN
src/tm/icons/Cut24.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/tm/icons/DecHeight24.gif
Normal file
After Width: | Height: | Size: 903 B |
BIN
src/tm/icons/DecPalIndex24.gif
Normal file
After Width: | Height: | Size: 239 B |
BIN
src/tm/icons/DecWidth24.gif
Normal file
After Width: | Height: | Size: 932 B |
BIN
src/tm/icons/Delete24.gif
Normal file
After Width: | Height: | Size: 249 B |
BIN
src/tm/icons/Down24.gif
Normal file
After Width: | Height: | Size: 635 B |
BIN
src/tm/icons/Dropper24.gif
Normal file
After Width: | Height: | Size: 150 B |
BIN
src/tm/icons/DropperCursor24.gif
Normal file
After Width: | Height: | Size: 162 B |
BIN
src/tm/icons/End24.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/tm/icons/Export24.gif
Normal file
After Width: | Height: | Size: 473 B |
BIN
src/tm/icons/FastForward24.gif
Normal file
After Width: | Height: | Size: 279 B |
BIN
src/tm/icons/Fill24.gif
Normal file
After Width: | Height: | Size: 171 B |
BIN
src/tm/icons/FillCursor24.gif
Normal file
After Width: | Height: | Size: 166 B |
BIN
src/tm/icons/Flip24.gif
Normal file
After Width: | Height: | Size: 148 B |
BIN
src/tm/icons/Forward24.gif
Normal file
After Width: | Height: | Size: 434 B |
BIN
src/tm/icons/Goto24.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/tm/icons/Home24.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/tm/icons/Icon24.gif
Normal file
After Width: | Height: | Size: 108 B |
BIN
src/tm/icons/Import24.gif
Normal file
After Width: | Height: | Size: 473 B |
BIN
src/tm/icons/IncHeight24.gif
Normal file
After Width: | Height: | Size: 904 B |
BIN
src/tm/icons/IncPalIndex24.gif
Normal file
After Width: | Height: | Size: 236 B |
BIN
src/tm/icons/IncWidth24.gif
Normal file
After Width: | Height: | Size: 932 B |
BIN
src/tm/icons/Line24.gif
Normal file
After Width: | Height: | Size: 124 B |
BIN
src/tm/icons/Minus24.gif
Normal file
After Width: | Height: | Size: 146 B |
BIN
src/tm/icons/Mirror24.gif
Normal file
After Width: | Height: | Size: 156 B |
BIN
src/tm/icons/Mover24.gif
Normal file
After Width: | Height: | Size: 136 B |
BIN
src/tm/icons/New24.gif
Normal file
After Width: | Height: | Size: 778 B |
BIN
src/tm/icons/Open24.gif
Normal file
After Width: | Height: | Size: 462 B |
BIN
src/tm/icons/Paste24.gif
Normal file
After Width: | Height: | Size: 374 B |
BIN
src/tm/icons/Play24.gif
Normal file
After Width: | Height: | Size: 247 B |
BIN
src/tm/icons/Plus24.gif
Normal file
After Width: | Height: | Size: 156 B |
BIN
src/tm/icons/Redo24.gif
Normal file
After Width: | Height: | Size: 243 B |
BIN
src/tm/icons/Rewind24.gif
Normal file
After Width: | Height: | Size: 279 B |
BIN
src/tm/icons/RotateLeft24.gif
Normal file
After Width: | Height: | Size: 148 B |
BIN
src/tm/icons/RotateRight24.gif
Normal file
After Width: | Height: | Size: 148 B |
BIN
src/tm/icons/RowDelete24.gif
Normal file
After Width: | Height: | Size: 248 B |
BIN
src/tm/icons/RowInsertAfter24.gif
Normal file
After Width: | Height: | Size: 239 B |
BIN
src/tm/icons/Save24.gif
Normal file
After Width: | Height: | Size: 266 B |
BIN
src/tm/icons/SaveAll24.gif
Normal file
After Width: | Height: | Size: 334 B |
BIN
src/tm/icons/SaveAs24.gif
Normal file
After Width: | Height: | Size: 348 B |
BIN
src/tm/icons/Selection24.gif
Normal file
After Width: | Height: | Size: 145 B |
BIN
src/tm/icons/ShiftDown24.gif
Normal file
After Width: | Height: | Size: 159 B |
BIN
src/tm/icons/ShiftLeft24.gif
Normal file
After Width: | Height: | Size: 158 B |
BIN
src/tm/icons/ShiftRight24.gif
Normal file
After Width: | Height: | Size: 159 B |
BIN
src/tm/icons/ShiftUp24.gif
Normal file
After Width: | Height: | Size: 158 B |