commit 69831530c18f0b4229942b727c97be8ebbfc99c6 Author: Iván Delgado Date: Tue Feb 2 23:23:26 2021 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb4dca6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +dist +compile.sh +.vscode \ No newline at end of file diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000..1db7929 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +Class-Path: . +Main-Class: TileMolester + diff --git a/TileMolester.iml b/TileMolester.iml new file mode 100644 index 0000000..1a05e40 --- /dev/null +++ b/TileMolester.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/TileMolester.jar b/TileMolester.jar new file mode 100644 index 0000000..998932b Binary files /dev/null and b/TileMolester.jar differ diff --git a/languages/language.properties b/languages/language.properties new file mode 100644 index 0000000..e0fc9a4 --- /dev/null +++ b/languages/language.properties @@ -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 \ No newline at end of file diff --git a/languages/language_en_US.properties b/languages/language_en_US.properties new file mode 100644 index 0000000..e0fc9a4 --- /dev/null +++ b/languages/language_en_US.properties @@ -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 \ No newline at end of file diff --git a/languages/language_it_IT.properties b/languages/language_it_IT.properties new file mode 100644 index 0000000..5ca571a --- /dev/null +++ b/languages/language_it_IT.properties @@ -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 \ No newline at end of file diff --git a/languages/language_no_NO.properties b/languages/language_no_NO.properties new file mode 100644 index 0000000..2a18400 --- /dev/null +++ b/languages/language_no_NO.properties @@ -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 \ No newline at end of file diff --git a/languages/language_sp_LA.properties b/languages/language_sp_LA.properties new file mode 100644 index 0000000..898fe0e --- /dev/null +++ b/languages/language_sp_LA.properties @@ -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 \ No newline at end of file diff --git a/languages/language_template b/languages/language_template new file mode 100644 index 0000000..817c359 --- /dev/null +++ b/languages/language_template @@ -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 = \ No newline at end of file diff --git a/resources/tmres.dtd b/resources/tmres.dtd new file mode 100644 index 0000000..bf24b42 --- /dev/null +++ b/resources/tmres.dtd @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/settings.dtd b/settings.dtd new file mode 100644 index 0000000..1481fcf --- /dev/null +++ b/settings.dtd @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/settings.xml b/settings.xml new file mode 100644 index 0000000..8335b68 --- /dev/null +++ b/settings.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/TileMolester.java b/src/TileMolester.java new file mode 100644 index 0000000..28a438a --- /dev/null +++ b/src/TileMolester.java @@ -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(); + } + +} \ No newline at end of file diff --git a/src/tm/FileImage.java b/src/tm/FileImage.java new file mode 100644 index 0000000..2c1af4a --- /dev/null +++ b/src/tm/FileImage.java @@ -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 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; + } + +} \ No newline at end of file diff --git a/src/tm/TMBitmapImporter.java b/src/tm/TMBitmapImporter.java new file mode 100644 index 0000000..2e2b543 --- /dev/null +++ b/src/tm/TMBitmapImporter.java @@ -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 0 && i < s.length() - 1) { + ext = s.substring(i+1).toLowerCase(); + } + return ext; + } + +} \ No newline at end of file diff --git a/src/tm/TMFileResources.java b/src/tm/TMFileResources.java new file mode 100644 index 0000000..2aa6fd1 --- /dev/null +++ b/src/tm/TMFileResources.java @@ -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\n"); + sb.append("\n"); + sb.append("\n"); + + sb.append(bookmarksToXML()); + sb.append(palettesToXML()); + + sb.append("\n"); + return sb.toString(); + } + +/** +* +* Return XML representation of bookmarks. +* +**/ + + public String bookmarksToXML() { + StringBuffer sb = new StringBuffer(); + sb.append(" \n"); + TMTreeNode[] children = bookmarkRoot.getChildren(); + for (int i=0; i\n"); + return sb.toString(); + } + +/** +* +* Returns XML representation of palettes. +* +**/ + + public String palettesToXML() { + StringBuffer sb = new StringBuffer(); + sb.append(" \n"); + TMTreeNode[] children = paletteRoot.getChildren(); + for (int i=0; i\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); + } + +} \ No newline at end of file diff --git a/src/tm/TMPalette.java b/src/tm/TMPalette.java new file mode 100644 index 0000000..122635c --- /dev/null +++ b/src/tm/TMPalette.java @@ -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= 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) { + // copy all old entries + for (int i=0; i> 16; + int targetG = (argb & 0x0000FF00) >> 8; + int targetB = (argb & 0x000000FF); + int bestEntry=0, bestDiff=1000000; + for (int i=0; i> 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 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 x2) { + x1 = selX2; + x2 = selX1; + } + if (y1 > y2) { + y1 = selY2; + y2 = selY1; + } + int dim = getScaledTileDim(); + g.setColor(Color.white); + g.drawRect(x1*dim, y1*dim, (x2-x1+1)*dim-1, (y2-y1+1)*dim-1); + } + } + +/////////////////////////////////////////////////////////////////////////////// +// Drawing/tool-related + +/** +* +* The mouse was clicked inside the canvas. +* +**/ + + public void mouseClicked(MouseEvent e) { + e.getClickCount(); + } + +/** +* +* Mouse entered the editor canvas. +* Switch to the proper cursor according to the current tool. +* +**/ + + public void mouseEntered(MouseEvent e) { + setToolTipText(""); + int tool = ui.getTool(); + if (tool == TMUI.SELECT_TOOL) { + setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + } + else if (tool == TMUI.ZOOM_TOOL) { + setCursor(zoomCursor); + } + else if (tool == TMUI.PICKUP_TOOL) { + setCursor(pickupCursor); + } + else if (tool == TMUI.BRUSH_TOOL) { + setCursor(brushCursor); + } + else if (tool == TMUI.LINE_TOOL) { + setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + } + else if (tool == TMUI.FILL_TOOL) { + setCursor(fillCursor); + } + else if (tool == TMUI.REPLACE_TOOL) { + setCursor(brushCursor); + } + else if (tool == TMUI.MOVE_TOOL) { + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + } + +/** +* +* The mouse exited the canvas. +* +**/ + + public void mouseExited(MouseEvent e) { + // TODO: remove statusbar coords text + } + +/** +* +* Initiates relevant tool action. +* +**/ + + public void mousePressed(MouseEvent e) { + // disable the keyboard events while a mouse action is in progress + view.setKeysEnabled(false); + // +// if (tool != TMUI.SELECT_TOOL) { + maybeApplySelection(); +// } + // get the current tool + int tool = ui.getTool(); + // get pixel coordinate + int x = (int)(e.getX() / scale); + int y = (int)(e.getY() / scale); + + if (tool == TMUI.SELECT_TOOL) { + // figure out starting (x,y) tile coords + selX1 = x / 8; + selY1 = y / 8; + // + isSelecting = false; + repaint(); + } + + else if (tool == TMUI.ZOOM_TOOL) { + if (e.getButton() == MouseEvent.BUTTON1) { + ui.doZoomInCommand(); + } + else { + ui.doZoomOutCommand(); + } + } + + else if (tool == TMUI.PICKUP_TOOL) { + // get pixel under cursor + int color = getPixel(x, y); + if (e.getButton() == MouseEvent.BUTTON1) { + // set as foreground color + ui.setFGColor(color); + } + else { + // set as background color + ui.setBGColor(color); + } + } + + else if (tool == TMUI.BRUSH_TOOL) { + drawColor = getDrawColor(e.getButton()); + drawLine(x, y, x, y, true); + redraw(); + lineX1 = x; + lineY1 = y; + } + + else if (tool == TMUI.LINE_TOOL) { + drawColor = getDrawColor(e.getButton()); + drawLine(x, y, x, y, false); + redraw(); + lineX1 = x; + lineY1 = y; + lineX2 = x; + lineY2 = y; + isDrawingLine = true; + } + + else if (tool == TMUI.FILL_TOOL) { + // get pixel under cursor + int color = getPixel(x, y); + // get draw color + drawColor = getDrawColor(e.getButton()); + if (drawColor != color) { + // fill! + filledColor = color; + // fillRecursive(x, y); + floodFill(x, y); + commitDrawingOperation("Flood Fill"); // i18n + } + } + + else if (tool == TMUI.REPLACE_TOOL) { + // get pixel under cursor + int color = getPixel(x, y); + drawColor = getDrawColor(e.getButton()); + // replace all occurences of color + for (int i=0; i= canvasWidth) { + x = canvasWidth - 1; + } + if (y < 0) { + y = 0; + } + else if (y >= canvasHeight) { + y = canvasHeight - 1; + } + + if (tool == TMUI.SELECT_TOOL) { + isSelecting = true; + // update selection + selX2 = x / 8; + selY2 = y / 8; + // + repaint(); + } + + else if (tool == TMUI.ZOOM_TOOL) { + } + + else if (tool == TMUI.PICKUP_TOOL) { + } + + else if (tool == TMUI.BRUSH_TOOL) { + drawLine(lineX1, lineY1, x, y, true); + redraw(); + lineX1 = x; + lineY1 = y; + } + + else if (tool == TMUI.LINE_TOOL) { + if (isDrawingLine) { + if ((x != lineX2) || (y != lineY2)) { // only if coordinates changed... + // "erase" old line + drawLine(lineX1, lineY1, lineX2, lineY2, false); + // draw new line + drawLine(lineX1, lineY1, x, y, false); + lineX2 = x; + lineY2 = y; + redraw(); + } + } + } + + else if (tool == TMUI.FILL_TOOL) { + } + + else if (tool == TMUI.REPLACE_TOOL) { + } + + else if (tool == TMUI.MOVE_TOOL) { + Point p = e.getPoint(); + int dx = p.x - moveMousePoint.x; + int dy = p.y - moveMousePoint.y; + + TMView view = (TMView)ui.getDesktop().getSelectedFrame(); + JScrollBar hsb = view.getScrollPane().getHorizontalScrollBar(); + JScrollBar vsb = view.getScrollPane().getVerticalScrollBar(); + int newXpos = hsb.getValue() + dx; + int newYpos = vsb.getValue() + dy; + + if (newXpos < hsb.getMinimum()) newXpos = hsb.getMinimum(); + else if (newXpos > hsb.getMaximum()) newXpos = hsb.getMaximum(); + if (newYpos < vsb.getMinimum()) newYpos = vsb.getMinimum(); + else if (newYpos > vsb.getMaximum()) newYpos = vsb.getMaximum(); + + hsb.setValue(newXpos); + hsb.revalidate(); + vsb.setValue(newYpos); + vsb.revalidate(); + view.getScrollPane().revalidate(); + view.revalidate(); + + moveMousePoint = new Point(p); + } + + // update status bar coords + currentCol = e.getX() / getScaledTileDim(); + currentRow = e.getY() / getScaledTileDim(); + ui.refreshStatusBar(); + } + +/** +* +* Terminates relevant tool action. +* +**/ + + public void mouseReleased(MouseEvent e) { + // get the current tool + int tool = ui.getTool(); + + if (tool == TMUI.SELECT_TOOL) { + if (isSelecting) { + // end selection + isSelecting = false; + if (selX1 > selX2) { + // swap + int temp = selX1; + selX1 = selX2; + selX2 = temp; + } + if (selY1 > selY2) { + // swap + int temp = selY1; + selY1 = selY2; + selY2 = temp; + } + + makeSelection(selX1, selY1, selX2-selX1+1, selY2-selY1+1); + + view.addReversibleAction( + new ReversibleNewSelectionAction(selectionCanvas,this) + ); + + } + else { + // encode selection, if there is one + } + if (hasSelection()) { + selectionCanvas.maybeShowPopup(e); + } + } + + else if (tool == TMUI.ZOOM_TOOL) { + } + + else if (tool == TMUI.PICKUP_TOOL) { + } + + else if (tool == TMUI.BRUSH_TOOL) { + commitDrawingOperation("Brush"); // i18n + } + + else if (tool == TMUI.LINE_TOOL) { + drawLine(lineX1, lineY1, lineX2, lineY2, false); + // draw the final line + isDrawingLine = false; + drawLine(lineX1, lineY1, lineX2, lineY2, true); + commitDrawingOperation("Line"); // i18n + } + + else if (tool == TMUI.FILL_TOOL) { + } + + else if (tool == TMUI.REPLACE_TOOL) { + } + + else if (tool == TMUI.MOVE_TOOL) { + } + + // enabled keyboard events again + view.setKeysEnabled(true); + } + +/** +* +* The mouse was moved within the canvas. +* +**/ + + public void mouseMoved(MouseEvent e) { + // get the current tool + int tool = ui.getTool(); + // get pixel coordinate + int x = (int)(e.getX() / scale); + int y = (int)(e.getY() / scale); + + if (tool == TMUI.PICKUP_TOOL) { + // get pixel under cursor + int color = getPixel(x, y); + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + setToolTipText("R:"+r+" G:"+g+" B:"+b); + } + + // update status bar coords + currentCol = e.getX() / getScaledTileDim(); + currentRow = e.getY() / getScaledTileDim(); + ui.refreshStatusBar(); + } + +/** +* +* Non-recursive algorithm that performs flood fill, starting at location (x,y). +* +**/ + + public void floodFill(int x, int y) { + Vector seeds = new Vector(); + seeds.add(new Point(x, y)); + while (!seeds.isEmpty()) { + Point p = (Point)seeds.remove(0); + x = p.x; + y = p.y; + if (getPixel(x, y) == drawColor) continue; + setPixelTraceable(x, y, drawColor); + + // find left side, filling along the way + int left = x-1; + while ((left >= 0) && (getPixel(left, y) == filledColor)) { + setPixelTraceable(left--, y, drawColor); + } + left++; + + // find right side, filling along the way + int right = x+1; + while ((right < canvasWidth) && (getPixel(right, y) == filledColor)) { + setPixelTraceable(right++, y, drawColor); + } + right--; + + // check row above + y--; + if (y >= 0) { + for (int i=left; i<=right; i++) { + if (getPixel(i, y) == filledColor) { + seeds.add(new Point(i, y)); + } + } + } + + // check row below + y += 2; + if (y < canvasHeight) { + for (int i=left; i<=right; i++) { + if (getPixel(i, y) == filledColor) { + seeds.add(new Point(i, y)); + } + } + } + } + } + +/** +* +* Replaces color with drawing color. +* This isn't used anymore because it's recursive at the pixel granularity, +* which caused stack overflows when filling moderately large areas. +* See the above method for a non-recursive implementation. +* +**/ + + private void fillRecursive(int x, int y) { + // bounds check + if ((x < 0) || (y < 0) || (x >= canvasWidth) || (y >= canvasHeight)) return; + + // color check + if (getPixel(x, y) != filledColor) return; + + // replace color + setPixelTraceable(x, y, drawColor); + + // call recursively + fillRecursive(x+1, y); + fillRecursive(x-1, y); + fillRecursive(x, y+1); + fillRecursive(x, y - 1); + } + +/** +* +* Draws a line from coordinates (x1,y1) to (x2,y2) in the pixel buffer, +* using the current draw color. +* +**/ + + private void drawLine(int x1, int y1, int x2, int y2, boolean trace) { + int dx = x2 - x1; + int dy = y2 - y1; + double delta; + if (dx != 0) { + delta = (double)dy / (double)dx; + } + else { + delta = 100000.0; // infinity + } + if (delta > 1.0 || delta < -1.0) { + delta = (double)dx / (double)dy; + // step in y direction + if (dy < 0) { + double i = x2; + for (int j=y2; j<=y1; j++) { + if (trace) + setPixelTraceable((int)i, j, drawColor); + else + xorPixel((int)i, j); + i += delta; + } + } + else { + double i = x1; + for (int j=y1; j<=y2; j++) { + if (trace) + setPixelTraceable((int)i, j, drawColor); + else + xorPixel((int)i, j); + i += delta; + } + } + } + else { + // step in x direction + if (dx < 0) { + double j = y2; + for (int i=x2; i<=x1; i++) { + if (trace) + setPixelTraceable(i, (int)j, drawColor); + else + xorPixel(i, (int)j); + j += delta; + } + } + else { + double j = y1; + for (int i=x1; i<=x2; i++) { + if (trace) + setPixelTraceable(i, (int)j, drawColor); + else + xorPixel(i, (int)j); + j += delta; + } + } + } + } + +/** +* +* Sets the pixel at coordinate (x,y) in the canvas to the specified value, +* and signals that it has been modified. +* +**/ + + protected void setPixelTraceable(int x, int y, int argb) { + // mark the tile as modified + tileModified(x/8, y/8); + + setPixel(x, y, argb); + } + +/** +* +* Marks the tile at location (col,row) in the grid as modified. +* +**/ + + private void tileModified(int col, int row) { + Point p = gridCoords[row][col]; + if (!modifiedTiles.contains(p)) { + modifiedTiles.add(p); + // save original pixels + copyTilePixelsToBuffer(col, row, tempPixels, 0); + IntBuffer ib = IntBuffer.allocate(8*8); + ib.put(tempPixels); + modifiedPixels.add(ib); + } + } + +/** +* +* Copies the pixels for the tile at location (x,y) in the grid to the +* specified buffer, starting at the specified offset. +* +**/ + + public void copyTilePixelsToBuffer(int x, int y, int[] buf, int ofs) { + int pixOfs = (y * 8 * canvasWidth) + (x * 8); + for (int i=0; i<8; i++) { + for (int j=0; j<8; j++) { + buf[ofs++] = pixels[pixOfs++]; + } + pixOfs += canvasWidth - 8; + } + } + +/** +* +* Copies the pixels in the buffer to the tile at location (x,y). +* +**/ + + public void copyBufferToTilePixels(int x, int y, int[] buf, int ofs) { + int pixOfs = (y * 8 * canvasWidth) + (x * 8); + for (int i=0; i<8; i++) { + for (int j=0; j<8; j++) { + pixels[pixOfs++] = buf[ofs++]; + } + pixOfs += canvasWidth - 8; + } + } + +/** +* +* Commits a drawing operation: Encodes all the affected tiles, +* sets modified, eventually adds to Undo buffer, redraws tiles +* +**/ + + private ReversibleTileModifyAction commitDrawingOperation(String name) { + return commitDrawingOperation(name,true); + } + + private ReversibleTileModifyAction commitDrawingOperation(String name, boolean undoable) { + // Undo-stuff + BookmarkItemNode bookmark = view.createBookmark(""); + + Point[] pts = new Point[modifiedTiles.size()]; + int[] oldPix = new int[pts.length * 8*8]; + int[] newPix = new int[pts.length * 8*8]; + + for (int i=0; i cols) { + w = cols - destx; + } + if (desty + h > rows) { + h = rows - desty; + } + + // Undo-stuff + for (int i=0; i> 1) * (blockLineSize << 1); + tileX <<= 1; + if ((mode == TileCodec.MODE_2D) && (tileX >= blockWidth)) { + relOfs += blockLineSize; + tileX -= blockWidth; + } + if ((tileY % 2) == 0) { + // even tile (0, 2, 4, ...) + relOfs += tileX * getTileIncrement(); + } + else { + // odd tile (1, 3, 5, ...) + tileX++; + relOfs += tileX * getTileIncrement(); + } + } + else { + relOfs += (tileY * blockLineSize) + (tileX * 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 stride. +* +**/ + + public int getStride() { + return (mode == TileCodec.MODE_1D) ? 0 : blockWidth-1; + } + +/** +* +* Turns the block grid on or off. +* +**/ + + public void setBlockGridVisible(boolean showBlockGrid) { + this.showBlockGrid = showBlockGrid; + } + +/** +* +* Gets the visibility status of the block grid. +* +**/ + + public boolean isBlockGridVisible() { + return showBlockGrid; + } + +/** +* +* Sets the block dimensions. +* +**/ + + public void setBlockDimensions(int blockWidth, int blockHeight) { + this.blockWidth = blockWidth; + this.blockHeight = blockHeight; + } + +/** +* +* Gets the block width (in # of tiles). +* +**/ + + public int getBlockWidth() { + return blockWidth; + } + +/** +* +* Gets the block height (in # of tiles). +* +**/ + + public int getBlockHeight() { + return blockHeight; + } + +/** +* +* Sets row-interleaved state. +* +**/ + + public void setRowInterleaveBlocks(boolean rowInterleaved) { + this.rowInterleaved = rowInterleaved; + } + +/** +* +* Gets row-interleaved state. +* +**/ + + public boolean getRowInterleaveBlocks() { + return rowInterleaved; + } + +} \ No newline at end of file diff --git a/src/tm/canvases/TMPixelCanvas.java b/src/tm/canvases/TMPixelCanvas.java new file mode 100644 index 0000000..9600936 --- /dev/null +++ b/src/tm/canvases/TMPixelCanvas.java @@ -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 bits 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 bits 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=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=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=0; y--) { + setPixel(x, y+1, getPixel(x, y)); + } + setPixel(x, 0, p0); + } + packPixels(); + } + +} \ No newline at end of file diff --git a/src/tm/canvases/TMSelectionCanvas.java b/src/tm/canvases/TMSelectionCanvas.java new file mode 100644 index 0000000..7a30b19 --- /dev/null +++ b/src/tm/canvases/TMSelectionCanvas.java @@ -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; ibits 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 bits +* 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= 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= 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=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> 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; ibits 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; + } + +} \ No newline at end of file diff --git a/src/tm/colorcodecs/DirectColorCodec.java b/src/tm/colorcodecs/DirectColorCodec.java new file mode 100644 index 0000000..8b11ad0 --- /dev/null +++ b/src/tm/colorcodecs/DirectColorCodec.java @@ -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); + } + +} \ No newline at end of file diff --git a/src/tm/colorcodecs/IndexedColorCodec.java b/src/tm/colorcodecs/IndexedColorCodec.java new file mode 100644 index 0000000..b0706b8 --- /dev/null +++ b/src/tm/colorcodecs/IndexedColorCodec.java @@ -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> 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; + } + +} \ No newline at end of file diff --git a/src/tm/filelistener/GameBoyAdvanceFileListener.java b/src/tm/filelistener/GameBoyAdvanceFileListener.java new file mode 100644 index 0000000..40e291d --- /dev/null +++ b/src/tm/filelistener/GameBoyAdvanceFileListener.java @@ -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> 8) & 0xFF); + data[0xBF] = (byte)(checkSum & 0xFF); + } + +} \ No newline at end of file diff --git a/src/tm/filelistener/GameBoyFileListener.java b/src/tm/filelistener/GameBoyFileListener.java new file mode 100644 index 0000000..27edcda --- /dev/null +++ b/src/tm/filelistener/GameBoyFileListener.java @@ -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> 8) & 0xFF); + data[0x14F] = (byte)(checkSum & 0xFF); + } + +} \ No newline at end of file diff --git a/src/tm/filelistener/INESFileListener.java b/src/tm/filelistener/INESFileListener.java new file mode 100644 index 0000000..23127f2 --- /dev/null +++ b/src/tm/filelistener/INESFileListener.java @@ -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> 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> 8) & 0xFF); + } + +} \ No newline at end of file diff --git a/src/tm/filelistener/TMFileListener.java b/src/tm/filelistener/TMFileListener.java new file mode 100644 index 0000000..bea82ef --- /dev/null +++ b/src/tm/filelistener/TMFileListener.java @@ -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 filelistener 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 true, +* 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); + +} \ No newline at end of file diff --git a/src/tm/fileselection/TMApprovedFileOpenChooser.java b/src/tm/fileselection/TMApprovedFileOpenChooser.java new file mode 100644 index 0000000..df39584 --- /dev/null +++ b/src/tm/fileselection/TMApprovedFileOpenChooser.java @@ -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 0 && i < s.length() - 1) { + ext = s.substring(i+1).toLowerCase(); + } + return ext; + } + +} \ No newline at end of file diff --git a/src/tm/fileselection/TMPaletteFileFilter.java b/src/tm/fileselection/TMPaletteFileFilter.java new file mode 100644 index 0000000..63cd899 --- /dev/null +++ b/src/tm/fileselection/TMPaletteFileFilter.java @@ -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; + } + +} \ No newline at end of file diff --git a/src/tm/fileselection/TMTileCodecFileFilter.java b/src/tm/fileselection/TMTileCodecFileFilter.java new file mode 100644 index 0000000..9d924ea --- /dev/null +++ b/src/tm/fileselection/TMTileCodecFileFilter.java @@ -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; + } + +} \ No newline at end of file diff --git a/src/tm/gfxlibs/BMPLoader.java b/src/tm/gfxlibs/BMPLoader.java new file mode 100644 index 0000000..1b08160 --- /dev/null +++ b/src/tm/gfxlibs/BMPLoader.java @@ -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 + +} + diff --git a/src/tm/gfxlibs/BMPReader.java b/src/tm/gfxlibs/BMPReader.java new file mode 100644 index 0000000..42e50ed --- /dev/null +++ b/src/tm/gfxlibs/BMPReader.java @@ -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( ) +// 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); + } +} \ No newline at end of file diff --git a/src/tm/gfxlibs/GIFOutputStream.java b/src/tm/gfxlibs/GIFOutputStream.java new file mode 100644 index 0000000..7408180 --- /dev/null +++ b/src/tm/gfxlibs/GIFOutputStream.java @@ -0,0 +1,1010 @@ +/* + Class: org.shetline.io.GIFOutputStream + + Copyright (c) 2000, 2001 by Kerry Shetline, kerry@shetline.com. + + This code is free for public use in any non-commercial application. All + other uses are restricted without prior consent of the author, Kerry + Shetline. The author assumes no liability for the suitability of this + code in any application. + + Note: This code does *NOT* implement LZW compression. While the output + of the compression routine is compatible with LZW, only a simple run- + length compression is performed. The degree of compression as compared + to LZW is not as high, but execution time is faster, and LZW licensing + issues are avoided. Depending on image size and image complexity, the + differences in compression may or may not have practical significance + for particular applications. + + Date Comments + ----------- -------- + 2000 SEP 30 First released version. + 2001 MAR 18 Replaced byte-by-byte specification of 256-color table with + a short code segment to generate the same table. + 2001 JUN 10 Added DITHERED_216_COLORS option. + 2001 AUG 21 Fixed a bug where single-color images would produce an + invalid GIF stream when using ORIGINAL_COLOR mode. GIF color + tables need to have at least two entries, so if the image only + has one color, an unused entry of either black or white is + added to the table to make it a valid length. +*/ + +package tm.gfxlibs; + +import java.io.*; +import java.awt.*; +import java.awt.image.*; +import java.util.Hashtable; +import java.util.Enumeration; + +public class GIFOutputStream extends FilterOutputStream +{ + public static final int ORIGINAL_COLOR = 0; + public static final int BLACK_AND_WHITE = 1; + public static final int GRAYSCALE_16 = 2; + public static final int GRAYSCALE_256 = 3; + public static final int STANDARD_16_COLORS = 4; + public static final int STANDARD_256_COLORS = 5; + public static final int DITHERED_216_COLORS = 6; + + public static final int NO_ERROR = 0; + public static final int IMAGE_LOAD_FAILED = 1; + public static final int TOO_MANY_COLORS = 2; + public static final int INVALID_COLOR_MODE = 3; + + protected static final int BLACK_INDEX = 0; + protected static final int WHITE_INDEX = 1; + + protected static final int[] standard16 = + { + 0x000000, + 0xFF0000, 0x00FF00, 0x0000FF, + 0x00FFFF, 0xFF00FF, 0xFFFF00, + 0x800000, 0x008000, 0x000080, + 0x008080, 0x800080, 0x808000, + 0x808080, 0xC0C0C0, + 0xFFFFFF + }; + + protected static final int[] standard256 = new int[256]; + + protected static int ditherPattern[][] = {{ 8, 184, 248, 216}, + {120, 56, 152, 88}, + { 40, 232, 24, 200}, + {168, 104, 136, 72}}; + + protected int errorStatus = NO_ERROR; + + static + { + // Set up a standard 256-color table. + + int n, j, r, g, b; + + standard256[0] = 0x000000; + + n = 40; + + // 0x33 multiples, starting at index 41 (black stored at index 40 gets replaced with 0xEE0000 + for (r = 0; r < 6; ++r) + for (g = 0; g < 6; ++g) + for (b = 0; b < 6; ++b) + standard256[n++] = 0x330000 * r | 0x003300 * g | 0x000033 * b; + + n = 1; + + for (j = 0; j < 10; ++j) { + // Shades of gray w/o 0x33 multiples, starting at index 1 + standard256[j + 1] = 0x111111 * n; + // Shades of blue w/o 0x33 multiples, starting at index 11 + standard256[j + 11] = 0x000011 * n; + // Shades of green w/o 0x33 multiples, starting at index 21 + standard256[j + 21] = 0x001100 * n; + // Shades of red w/o 0x33 multiples, starting at index 31 + standard256[j + 31] = 0x110000 * n; + + ++n; + + if (n % 3 == 0) + ++n; + } + } + + public static int writeGIF(OutputStream out, Image image) throws IOException + { + return writeGIF(out, image, ORIGINAL_COLOR, null); + } + + public static int writeGIF(OutputStream out, Image image, int colorMode) throws IOException + { + return writeGIF(out, image, colorMode, null); + } + + public static int writeGIF(OutputStream out, Image image, int colorMode, Color transparentColor) throws IOException + { + GIFOutputStream gifOut = new GIFOutputStream(out); + + gifOut.write(image, colorMode, transparentColor); + + int res = gifOut.getErrorStatus(); + + gifOut.close(); + + return res; + } + + public GIFOutputStream(OutputStream out) + { + super(out); + } + + public int getErrorStatus() { return errorStatus; } + + public void write(Image image) throws IOException + { + write(image, ORIGINAL_COLOR, null); + } + + public void write(Image image, int colorMode) throws IOException + { + write(image, colorMode, null); + } + + public void write(Image image, Color transparentColor) throws IOException + { + write(image, ORIGINAL_COLOR, transparentColor); + } + + public void write(Image image, int colorMode, Color transparentColor) throws IOException + { + errorStatus = NO_ERROR; + + if (image == null) + return; + + PixelGrabber pg = new PixelGrabber(image, 0, 0, -1, -1, true); + + try { + pg.grabPixels(); + } catch (InterruptedException e) { + errorStatus = IMAGE_LOAD_FAILED; + return; + } + + if ((pg.status() & ImageObserver.ABORT) != 0) { + errorStatus = IMAGE_LOAD_FAILED; + return; + } + + int[] pixels = (int[]) pg.getPixels(); + int width = pg.getWidth(); + int height = pg.getHeight(); + int colorCount = 0; + int[] colorTable = null; + byte[] bytePixels = null; + +////// do color reduction +/* int[][] pixxies = new int[height][width]; + for (int i=0; i 256) { + errorStatus = TOO_MANY_COLORS; + return; + } + colorTable = createColorTable(colorSet, colorCount); + bytePixels = createBytePixels(pixels, colorSet); + break; + + case BLACK_AND_WHITE: + colorCount = 2; + colorTable = createBWTable(); + bytePixels = createBWBytePixels(pixels); + break; + + case GRAYSCALE_16: + colorCount = 16; + colorTable = create16GrayTable(); + bytePixels = create16GrayBytePixels(pixels); + break; + + case GRAYSCALE_256: + colorCount = 256; + colorTable = create256GrayTable(); + bytePixels = create256GrayBytePixels(pixels); + break; + + case STANDARD_16_COLORS: + colorCount = 16; + colorTable = createStd16ColorTable(); + bytePixels = createStd16ColorBytePixels(pixels); + break; + + case STANDARD_256_COLORS: + colorCount = 256; + colorTable = createStd256ColorTable(); + bytePixels = createStd256ColorBytePixels(pixels, width, false); + break; + + case DITHERED_216_COLORS: + colorCount = 216; + colorTable = createStd216ColorTable(); + bytePixels = createStd256ColorBytePixels(pixels, width, true); + break; + + default: + errorStatus = INVALID_COLOR_MODE; + return; + } + + pixels = null; + + int cc1 = colorCount - 1; + int bitsPerPixel = 0; + + while (cc1 != 0) { + ++bitsPerPixel; + cc1 >>= 1; + } + + writeGIFHeader(width, height, bitsPerPixel); + + writeColorTable(colorTable, bitsPerPixel); + + if (transparentColor != null) + writeGraphicControlExtension(transparentColor, colorTable); + + writeImageDescriptor(width, height); + + writeCompressedImageData(bytePixels, bitsPerPixel); + + write(0x00); // Terminate picture data. + + write(0x3B); // GIF file terminator. + } + + protected Hashtable getColorSet(int[] pixels) + { + Hashtable colorSet = new Hashtable(); + boolean[] checked = new boolean[pixels.length]; + int needsChecking = pixels.length; + int color; + int colorIndex = 0; + Integer key; + + for (int j = 0; j < pixels.length && needsChecking > 0; ++j) { + if (!checked[j]) { + color = pixels[j] & 0x00FFFFFF; + checked[j] = true; + --needsChecking; + + key = new Integer(color); + colorSet.put(key, new Integer(colorIndex)); + if (++colorIndex > 256) + break; + + for (int j2 = j + 1; j2 < pixels.length; ++j2) { + if ((pixels[j2] & 0x00FFFFFF) == color) { + checked[j2] = true; + --needsChecking; + } + } + } + } + + if (colorIndex == 1) { + if (colorSet.get(new Integer(0)) == null) + colorSet.put(new Integer(0), new Integer(1)); + else + colorSet.put(new Integer(0xFFFFFF), new Integer(1)); + } + + return colorSet; + } + + protected int[] createColorTable(Hashtable colorSet, int colorCount) + { + int[] colorTable = new int[colorCount]; + Integer key; + + for (Enumeration e = colorSet.keys(); e.hasMoreElements(); ) { + key = (Integer) e.nextElement(); + colorTable[((Integer) colorSet.get(key)).intValue()] = key.intValue(); + } + + return colorTable; + } + + protected byte[] createBytePixels(int[] pixels, Hashtable colorSet) + { + byte[] bytePixels = new byte[pixels.length]; + Integer key; + int colorIndex; + + for (int j = 0; j < pixels.length; ++j) { + key = new Integer(pixels[j] & 0x00FFFFFF); + colorIndex = ((Integer) colorSet.get(key)).intValue(); + bytePixels[j] = (byte) colorIndex; + } + + return bytePixels; + } + + protected int[] createBWTable() + { + int[] colorTable = new int[2]; + + colorTable[BLACK_INDEX] = 0x000000; + colorTable[WHITE_INDEX] = 0xFFFFFF; + + return colorTable; + } + + protected byte[] createBWBytePixels(int[] pixels) + { + byte[] bytePixels = new byte[pixels.length]; + + for (int j = 0; j < pixels.length; ++j) { + if (grayscaleValue(pixels[j]) < 0x80) + bytePixels[j] = (byte) BLACK_INDEX; + else + bytePixels[j] = (byte) WHITE_INDEX; + } + + return bytePixels; + } + + protected int[] create16GrayTable() + { + int[] colorTable = new int[16]; + + for (int j = 0; j < 16; ++j) + colorTable[j] = 0x111111 * j; + + return colorTable; + } + + protected byte[] create16GrayBytePixels(int[] pixels) + { + byte[] bytePixels = new byte[pixels.length]; + + for (int j = 0; j < pixels.length; ++j) { + bytePixels[j] = (byte) (grayscaleValue(pixels[j]) / 16); + } + + return bytePixels; + } + + protected int[] create256GrayTable() + { + int[] colorTable = new int[256]; + + for (int j = 0; j < 256; ++j) + colorTable[j] = 0x010101 * j; + + return colorTable; + } + + protected byte[] create256GrayBytePixels(int[] pixels) + { + byte[] bytePixels = new byte[pixels.length]; + + for (int j = 0; j < pixels.length; ++j) { + bytePixels[j] = (byte) grayscaleValue(pixels[j]); + } + + return bytePixels; + } + + protected int[] createStd16ColorTable() + { + int[] colorTable = new int[16]; + + System.arraycopy(standard16, 0, colorTable, 0, 16); + + return colorTable; + } + + protected byte[] createStd16ColorBytePixels(int[] pixels) + { + byte[] bytePixels = new byte[pixels.length]; + int color; + int minError = 0; + int error; + int minIndex; + + for (int j = 0; j < pixels.length; ++j) { + color = pixels[j] & 0xFFFFFF; + minIndex = -1; + + for (int k = 0; k < 16; ++k) { + error = colorMatchError(color, standard16[k]); + if (error < minError || minIndex < 0) { + minError = error; + minIndex = k; + } + } + + bytePixels[j] = (byte) minIndex; + } + + return bytePixels; + } + + protected int[] createStd256ColorTable() + { + int[] colorTable = new int[256]; + + System.arraycopy(standard256, 0, colorTable, 0, 256); + + return colorTable; + } + + protected int[] createStd216ColorTable() + { + int[] colorTable = new int[216]; + + colorTable[0] = 0x000000; + + System.arraycopy(standard256, 41, colorTable, 1, 215); + + return colorTable; + } + + protected byte[] createStd256ColorBytePixels(int[] pixels, int width, boolean dither) + { + byte[] bytePixels = new byte[pixels.length]; + int color; + int minError = 0; + int error; + int minIndex; + int sampleIndex; + int r, g, b; + int r2, g2, b2; + int x, y; + int threshold; + + for (int j = 0; j < pixels.length; ++j) { + color = pixels[j] & 0xFFFFFF; + minIndex = -1; + + r = (color & 0xFF0000) >> 16; + g = (color & 0x00FF00) >> 8; + b = color & 0x0000FF; + + r2 = r / 0x33; + g2 = g / 0x33; + b2 = b / 0x33; + + if (dither) { + x = j % width; + y = j / width; + threshold = ditherPattern[x % 4][y % 4] / 5; + + if (r2 < 5 && r % 0x33 >= threshold) + ++r2; + + if (g2 < 5 && g % 0x33 >= threshold) + ++g2; + + if (b2 < 5 && b % 0x33 >= threshold) + ++b2; + + bytePixels[j] = (byte) (r2 * 36 + g2 * 6 + b2); + } + else { + // Try to match color to a 0x33-multiple color. + + for (int r0 = r2; r0 <= r2 + 1 && r0 < 6; ++r0) { + for (int g0 = g2; g0 <= g2 + 1 && g0 < 6; ++g0) { + for (int b0 = b2; b0 <= b2 + 1 && b0 < 6; ++b0) { + sampleIndex = 40 + r0 * 36 + g0 * 6 + b0; + if (sampleIndex == 40) + sampleIndex = 0; + + error = colorMatchError(color, standard256[sampleIndex]); + if (error < minError || minIndex < 0) { + minError = error; + minIndex = sampleIndex; + } + } + } + } + + int shadeBase; + int shadeIndex; + + // Try to match color to a 0x11-multiple pure primary shade. + + if (r > g && r > b) { + shadeBase = 30; + shadeIndex = (r + 8) / 0x11; + } + else if (g > r && g > b) { + shadeBase = 20; + shadeIndex = (g + 8) / 0x11; + } + else { + shadeBase = 10; + shadeIndex = (b + 8) / 0x11; + } + + if (shadeIndex > 0) { + shadeIndex -= (shadeIndex / 3); + sampleIndex = shadeBase + shadeIndex; + error = colorMatchError(color, standard256[sampleIndex]); + if (error < minError || minIndex < 0) { + minError = error; + minIndex = sampleIndex; + } + } + + // Try to match color to a 0x11-multiple gray. + + shadeIndex = (grayscaleValue(color) + 8) / 0x11; + if (shadeIndex > 0) { + shadeIndex -= (shadeIndex / 3); + sampleIndex = shadeIndex; + error = colorMatchError(color, standard256[sampleIndex]); + if (error < minError || minIndex < 0) { + minError = error; + minIndex = sampleIndex; + } + } + + bytePixels[j] = (byte) minIndex; + } + } + + return bytePixels; + } + + protected int grayscaleValue(int color) + { + int r = (color & 0xFF0000) >> 16; + int g = (color & 0x00FF00) >> 8; + int b = color & 0x0000FF; + + return (r * 30 + g * 59 + b * 11) / 100; + } + + protected int colorMatchError(int color1, int color2) + { + int r1 = (color1 & 0xFF0000) >> 16; + int g1 = (color1 & 0x00FF00) >> 8; + int b1 = color1 & 0x0000FF; + int r2 = (color2 & 0xFF0000) >> 16; + int g2 = (color2 & 0x00FF00) >> 8; + int b2 = color2 & 0x0000FF; + int dr = (r2 - r1) * 30; + int dg = (g2 - g1) * 59; + int db = (b2 - b1) * 11; + + return (dr * dr + dg * dg + db * db) / 100; + } + + protected void writeGIFHeader(int width, int height, int bitsPerPixel) throws IOException + { + write((int) 'G'); + write((int) 'I'); + write((int) 'F'); + write((int) '8'); + write((int) '9'); + write((int) 'a'); + + writeGIFWord(width); + writeGIFWord(height); + + int packedBits = 0x80; // Yes, there is a global color table, not ordered. + + packedBits |= ((bitsPerPixel - 1) << 4) | (bitsPerPixel - 1); + + write(packedBits); + + write(0); // Background color index -- not used. + + write(0); // Aspect ratio index -- not specified. + } + + protected void writeColorTable(int[] colorTable, int bitsPerPixel) throws IOException + { + int colorCount = 1 << bitsPerPixel; + + for (int j = 0; j < colorCount; ++j) { + if (j < colorTable.length) + writeGIFColor(colorTable[j]); + else + writeGIFColor(0); + } + } + + protected void writeGraphicControlExtension(Color transparentColor, + int[] colorTable) throws IOException + { + for (int j = 0; j < colorTable.length; ++j) { + if (colorTable[j] == (transparentColor.getRGB() & 0xFFFFFF)) { + write(0x21); // Extension identifier. + write(0xF9); // Graphic Control Extension identifier. + write(0x04); // Block size, always 4. + write(0x01); // Sets transparent color bit. Other bits in this + // packed field should be zero. + write(0x00); // Two bytes of delay time -- not used. + write(0x00); + write(j); // Index of transparent color. + write(0x00); // Block terminator. + } + } + } + + protected void writeImageDescriptor(int width, int height) throws IOException + { + write(0x2C); // Image descriptor identifier; + + writeGIFWord(0); // left postion; + writeGIFWord(0); // top postion; + writeGIFWord(width); + writeGIFWord(height); + + write(0); // No local color table, not interlaced. + } + + protected void writeGIFWord(short word) throws IOException + { + writeGIFWord((int) word); + } + + protected void writeGIFWord(int word) throws IOException + { + write(word & 0xFF); + write((word & 0xFF00) >> 8); + } + + protected void writeGIFColor(Color color) throws IOException + { + writeGIFColor(color.getRGB()); + } + + protected void writeGIFColor(int color) throws IOException + { + write((color & 0xFF0000) >> 16); + write((color & 0xFF00) >> 8); + write(color & 0xFF); + } + + + /********************************************************************\ + | | + | The following code is based on C code for GIF compression | + | obtained from http://www.boutell.com | + | | + | Based on GIFENCOD by David Rowley | + | Modified by Marcel Wijkstra | + | One version, Copyright (C) 1989 by Jef Poskanzer. | + | Heavily modified by Mouse, 1998-02-12. | + | | + | And now, modified and rendered in Java by Kerry Shetline, 2000, | + | kerry@shetline.com | + | | + \********************************************************************/ + + protected int rl_pixel; + protected int rl_basecode; + protected int rl_count; + protected int rl_table_pixel; + protected int rl_table_max; + protected boolean just_cleared; + protected int out_bits; + protected int out_bits_init; + protected int out_count; + protected int out_bump; + protected int out_bump_init; + protected int out_clear; + protected int out_clear_init; + protected int max_ocodes; + protected int code_clear; + protected int code_eof; + protected int obuf; + protected int obits; + protected byte[] oblock = new byte[256]; + protected int oblen; + + protected final static int GIFBITS = 12; + + protected void writeCompressedImageData(byte[] bytePixels, int bitsPerPixel) + throws IOException + { + int init_bits = bitsPerPixel; + + if (init_bits < 2) + init_bits = 2; + + write(init_bits); + + int c; + + obuf = 0; + obits = 0; + oblen = 0; + code_clear = 1 << init_bits; + code_eof = code_clear + 1; + rl_basecode = code_eof + 1; + out_bump_init = (1 << init_bits) - 1; + /* for images with a lot of runs, making out_clear_init larger will + give better compression. */ + out_clear_init = (init_bits <= 2) ? 9 : (out_bump_init - 1); + out_bits_init = init_bits + 1; + max_ocodes = (1 << GIFBITS) - ((1 << (out_bits_init - 1)) + 3); + did_clear(); + output(code_clear); + rl_count = 0; + + for (int j = 0; j < bytePixels.length; ++j) { + c = (int) bytePixels[j]; + if (c < 0) + c += 256; + + if ((rl_count > 0) && (c != rl_pixel)) + rl_flush(); + + if (rl_pixel == c) { + rl_count++; + } + else { + rl_pixel = c; + rl_count = 1; + } + } + + if (rl_count > 0) + rl_flush(); + + output(code_eof); + output_flush(); + } + + + protected void write_block() throws IOException + { + write(oblen); + write(oblock, 0, oblen); + oblen = 0; + } + + protected void block_out(int c) throws IOException + { + oblock[oblen++] = (byte) c; + if (oblen >= 255) + write_block(); + } + + protected void block_flush() throws IOException + { + if (oblen > 0) + write_block(); + } + + protected void output(int val) throws IOException + { + obuf |= val << obits; + obits += out_bits; + while (obits >= 8) { + block_out(obuf & 0xFF); + obuf >>= 8; + obits -= 8; + } + } + + protected void output_flush() throws IOException + { + if (obits > 0) + block_out(obuf); + block_flush(); + } + + protected void did_clear() throws IOException + { + out_bits = out_bits_init; + out_bump = out_bump_init; + out_clear = out_clear_init; + out_count = 0; + rl_table_max = 0; + just_cleared = true; + } + + protected void output_plain(int c) throws IOException + { + just_cleared = false; + output(c); + out_count++; + if (out_count >= out_bump) { + out_bits++; + out_bump += 1 << (out_bits - 1); + } + if (out_count >= out_clear) { + output(code_clear); + did_clear(); + } + } + + protected int isqrt(int x) + { + int r; + int v; + + if (x < 2) + return x; + + for (v = x, r = 1; v != 0; v >>= 2, r <<= 1); + + while (true) { + v = ((x / r) + r) / 2; + if ((v == r) || (v == r + 1)) + return r; + r = v; + } + } + + protected int compute_triangle_count(int count, int nrepcodes) + { + int perrep; + int cost; + + cost = 0; + perrep = (nrepcodes * (nrepcodes +1 )) / 2; + while (count >= perrep) { + cost += nrepcodes; + count -= perrep; + } + if (count > 0) { + int n = isqrt(count); + while ((n * (n + 1)) >= 2 * count) + n--; + while ((n * (n + 1)) < 2 * count) + n++; + cost += n; + } + + return cost; + } + + protected void max_out_clear() + { + out_clear = max_ocodes; + } + + protected void reset_out_clear() throws IOException + { + out_clear = out_clear_init; + if (out_count >= out_clear) { + output(code_clear); + did_clear(); + } + } + + protected void rl_flush_fromclear(int count) throws IOException + { + int n; + + max_out_clear(); + rl_table_pixel = rl_pixel; + n = 1; + while (count > 0) { + if (n == 1) { + rl_table_max = 1; + output_plain(rl_pixel); + count--; + } + else if (count >= n) { + rl_table_max = n; + output_plain(rl_basecode + n - 2); + count -= n; + } + else if (count == 1) { + rl_table_max++; + output_plain(rl_pixel); + count = 0; + } + else { + rl_table_max++; + output_plain(rl_basecode + count - 2); + count = 0; + } + + if (out_count == 0) + n = 1; + else + n++; + } + + reset_out_clear(); + } + + protected void rl_flush_clearorrep(int count) throws IOException + { + int withclr; + + withclr = 1 + compute_triangle_count(count, max_ocodes); + if (withclr < count) { + output(code_clear); + did_clear(); + rl_flush_fromclear(count); + } + else { + for (; count > 0; count--) + output_plain(rl_pixel); + } + } + + protected void rl_flush_withtable(int count) throws IOException + { + int repmax; + int repleft; + int leftover; + + repmax = count / rl_table_max; + leftover = count % rl_table_max; + repleft = (leftover != 0 ? 1 : 0); + if (out_count + repmax + repleft > max_ocodes) { + repmax = max_ocodes - out_count; + leftover = count - (repmax * rl_table_max); + repleft = 1 + compute_triangle_count(leftover, max_ocodes); + } + + if (1 + compute_triangle_count(count,max_ocodes) < repmax + repleft) { + output(code_clear); + did_clear(); + rl_flush_fromclear(count); + return; + } + + max_out_clear(); + for (; repmax > 0; repmax--) + output_plain(rl_basecode + rl_table_max - 2); + if (leftover != 0) { + if (just_cleared) { + rl_flush_fromclear(leftover); + } + else if (leftover == 1) { + output_plain(rl_pixel); + } + else { + output_plain(rl_basecode + leftover - 2); + } + } + reset_out_clear(); + } + + protected void rl_flush() throws IOException + { + int table_reps; + int table_extra; + + if (rl_count == 1) { + output_plain(rl_pixel); + rl_count = 0; + return; + } + if (just_cleared) { + rl_flush_fromclear(rl_count); + } + else if ((rl_table_max < 2) || (rl_table_pixel != rl_pixel)) { + rl_flush_clearorrep(rl_count); + } + else { + rl_flush_withtable(rl_count); + } + + rl_count = 0; + } + + /******** END OF IMPORTED GIF COMPRESSION CODE ********/ +} diff --git a/src/tm/gfxlibs/ImageEncoder.java b/src/tm/gfxlibs/ImageEncoder.java new file mode 100644 index 0000000..6133916 --- /dev/null +++ b/src/tm/gfxlibs/ImageEncoder.java @@ -0,0 +1,271 @@ +// ImageEncoder - abstract class for writing out an image +// +// Copyright (C) 1996 by Jef Poskanzer . 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. +//

+// A framework for classes that encode and write out an image in +// a particular file format. +//

+// 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. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @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(); + } + + } diff --git a/src/tm/gfxlibs/PCXEncoder.java b/src/tm/gfxlibs/PCXEncoder.java new file mode 100644 index 0000000..183c1ce --- /dev/null +++ b/src/tm/gfxlibs/PCXEncoder.java @@ -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> 8) & 0xFF); + } + + private static void emitZeroes(int c) { + for (int i=0; i> 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++; + } + } + } + +} \ No newline at end of file diff --git a/src/tm/gfxlibs/PCXReader.java b/src/tm/gfxlibs/PCXReader.java new file mode 100644 index 0000000..bdb47be --- /dev/null +++ b/src/tm/gfxlibs/PCXReader.java @@ -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 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; + } + } +} + + diff --git a/src/tm/gfxlibs/PngEncoder.java b/src/tm/gfxlibs/PngEncoder.java new file mode 100644 index 0000000..e8b7ea1 --- /dev/null +++ b/src/tm/gfxlibs/PngEncoder.java @@ -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> 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 ); + } +} + diff --git a/src/tm/gfxlibs/Quantize.java b/src/tm/gfxlibs/Quantize.java new file mode 100644 index 0000000..aab2fa1 --- /dev/null +++ b/src/tm/gfxlibs/Quantize.java @@ -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 ImageMagick. 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.

+ * + * Here is the copyright notice from ImageMagick: + * + *

+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%  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.       %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ * + * + * @version 0.90 19 Sep 2000 + * @author Adam Doppelt + */ + +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); + } + } + } +} diff --git a/src/tm/icons/Add24.gif b/src/tm/icons/Add24.gif new file mode 100644 index 0000000..fecc7a8 Binary files /dev/null and b/src/tm/icons/Add24.gif differ diff --git a/src/tm/icons/Back24.gif b/src/tm/icons/Back24.gif new file mode 100644 index 0000000..787518c Binary files /dev/null and b/src/tm/icons/Back24.gif differ diff --git a/src/tm/icons/Bookmarks24.gif b/src/tm/icons/Bookmarks24.gif new file mode 100644 index 0000000..3b18044 Binary files /dev/null and b/src/tm/icons/Bookmarks24.gif differ diff --git a/src/tm/icons/Brush24.gif b/src/tm/icons/Brush24.gif new file mode 100644 index 0000000..f8e90cb Binary files /dev/null and b/src/tm/icons/Brush24.gif differ diff --git a/src/tm/icons/BrushCursor24.gif b/src/tm/icons/BrushCursor24.gif new file mode 100644 index 0000000..468cf26 Binary files /dev/null and b/src/tm/icons/BrushCursor24.gif differ diff --git a/src/tm/icons/ColorReplacer24.gif b/src/tm/icons/ColorReplacer24.gif new file mode 100644 index 0000000..bb0c604 Binary files /dev/null and b/src/tm/icons/ColorReplacer24.gif differ diff --git a/src/tm/icons/ColumnDelete24.gif b/src/tm/icons/ColumnDelete24.gif new file mode 100644 index 0000000..cdd09d3 Binary files /dev/null and b/src/tm/icons/ColumnDelete24.gif differ diff --git a/src/tm/icons/ColumnInsertAfter24.gif b/src/tm/icons/ColumnInsertAfter24.gif new file mode 100644 index 0000000..f9eba5f Binary files /dev/null and b/src/tm/icons/ColumnInsertAfter24.gif differ diff --git a/src/tm/icons/Copy24.gif b/src/tm/icons/Copy24.gif new file mode 100644 index 0000000..c665d07 Binary files /dev/null and b/src/tm/icons/Copy24.gif differ diff --git a/src/tm/icons/Cut24.gif b/src/tm/icons/Cut24.gif new file mode 100644 index 0000000..5c37d3a Binary files /dev/null and b/src/tm/icons/Cut24.gif differ diff --git a/src/tm/icons/DecHeight24.gif b/src/tm/icons/DecHeight24.gif new file mode 100644 index 0000000..d4d811a Binary files /dev/null and b/src/tm/icons/DecHeight24.gif differ diff --git a/src/tm/icons/DecPalIndex24.gif b/src/tm/icons/DecPalIndex24.gif new file mode 100644 index 0000000..5b35172 Binary files /dev/null and b/src/tm/icons/DecPalIndex24.gif differ diff --git a/src/tm/icons/DecWidth24.gif b/src/tm/icons/DecWidth24.gif new file mode 100644 index 0000000..aee6ecc Binary files /dev/null and b/src/tm/icons/DecWidth24.gif differ diff --git a/src/tm/icons/Delete24.gif b/src/tm/icons/Delete24.gif new file mode 100644 index 0000000..96d799a Binary files /dev/null and b/src/tm/icons/Delete24.gif differ diff --git a/src/tm/icons/Down24.gif b/src/tm/icons/Down24.gif new file mode 100644 index 0000000..2c47af8 Binary files /dev/null and b/src/tm/icons/Down24.gif differ diff --git a/src/tm/icons/Dropper24.gif b/src/tm/icons/Dropper24.gif new file mode 100644 index 0000000..b169539 Binary files /dev/null and b/src/tm/icons/Dropper24.gif differ diff --git a/src/tm/icons/DropperCursor24.gif b/src/tm/icons/DropperCursor24.gif new file mode 100644 index 0000000..6689523 Binary files /dev/null and b/src/tm/icons/DropperCursor24.gif differ diff --git a/src/tm/icons/End24.gif b/src/tm/icons/End24.gif new file mode 100644 index 0000000..2eba152 Binary files /dev/null and b/src/tm/icons/End24.gif differ diff --git a/src/tm/icons/Export24.gif b/src/tm/icons/Export24.gif new file mode 100644 index 0000000..ee52341 Binary files /dev/null and b/src/tm/icons/Export24.gif differ diff --git a/src/tm/icons/FastForward24.gif b/src/tm/icons/FastForward24.gif new file mode 100644 index 0000000..814664c Binary files /dev/null and b/src/tm/icons/FastForward24.gif differ diff --git a/src/tm/icons/Fill24.gif b/src/tm/icons/Fill24.gif new file mode 100644 index 0000000..000fa32 Binary files /dev/null and b/src/tm/icons/Fill24.gif differ diff --git a/src/tm/icons/FillCursor24.gif b/src/tm/icons/FillCursor24.gif new file mode 100644 index 0000000..1289758 Binary files /dev/null and b/src/tm/icons/FillCursor24.gif differ diff --git a/src/tm/icons/Flip24.gif b/src/tm/icons/Flip24.gif new file mode 100644 index 0000000..219a9d5 Binary files /dev/null and b/src/tm/icons/Flip24.gif differ diff --git a/src/tm/icons/Forward24.gif b/src/tm/icons/Forward24.gif new file mode 100644 index 0000000..1936fd4 Binary files /dev/null and b/src/tm/icons/Forward24.gif differ diff --git a/src/tm/icons/Goto24.gif b/src/tm/icons/Goto24.gif new file mode 100644 index 0000000..60a35dd Binary files /dev/null and b/src/tm/icons/Goto24.gif differ diff --git a/src/tm/icons/Home24.gif b/src/tm/icons/Home24.gif new file mode 100644 index 0000000..a25dee2 Binary files /dev/null and b/src/tm/icons/Home24.gif differ diff --git a/src/tm/icons/Icon24.gif b/src/tm/icons/Icon24.gif new file mode 100644 index 0000000..80c8d1d Binary files /dev/null and b/src/tm/icons/Icon24.gif differ diff --git a/src/tm/icons/Import24.gif b/src/tm/icons/Import24.gif new file mode 100644 index 0000000..6a34d20 Binary files /dev/null and b/src/tm/icons/Import24.gif differ diff --git a/src/tm/icons/IncHeight24.gif b/src/tm/icons/IncHeight24.gif new file mode 100644 index 0000000..6c386c6 Binary files /dev/null and b/src/tm/icons/IncHeight24.gif differ diff --git a/src/tm/icons/IncPalIndex24.gif b/src/tm/icons/IncPalIndex24.gif new file mode 100644 index 0000000..a47f3b3 Binary files /dev/null and b/src/tm/icons/IncPalIndex24.gif differ diff --git a/src/tm/icons/IncWidth24.gif b/src/tm/icons/IncWidth24.gif new file mode 100644 index 0000000..e8cd993 Binary files /dev/null and b/src/tm/icons/IncWidth24.gif differ diff --git a/src/tm/icons/Line24.gif b/src/tm/icons/Line24.gif new file mode 100644 index 0000000..d13bc42 Binary files /dev/null and b/src/tm/icons/Line24.gif differ diff --git a/src/tm/icons/Minus24.gif b/src/tm/icons/Minus24.gif new file mode 100644 index 0000000..a2a2bc0 Binary files /dev/null and b/src/tm/icons/Minus24.gif differ diff --git a/src/tm/icons/Mirror24.gif b/src/tm/icons/Mirror24.gif new file mode 100644 index 0000000..064c604 Binary files /dev/null and b/src/tm/icons/Mirror24.gif differ diff --git a/src/tm/icons/Mover24.gif b/src/tm/icons/Mover24.gif new file mode 100644 index 0000000..f3b169c Binary files /dev/null and b/src/tm/icons/Mover24.gif differ diff --git a/src/tm/icons/New24.gif b/src/tm/icons/New24.gif new file mode 100644 index 0000000..1cc488d Binary files /dev/null and b/src/tm/icons/New24.gif differ diff --git a/src/tm/icons/Open24.gif b/src/tm/icons/Open24.gif new file mode 100644 index 0000000..2086bc2 Binary files /dev/null and b/src/tm/icons/Open24.gif differ diff --git a/src/tm/icons/Paste24.gif b/src/tm/icons/Paste24.gif new file mode 100644 index 0000000..26cc4c5 Binary files /dev/null and b/src/tm/icons/Paste24.gif differ diff --git a/src/tm/icons/Play24.gif b/src/tm/icons/Play24.gif new file mode 100644 index 0000000..572467c Binary files /dev/null and b/src/tm/icons/Play24.gif differ diff --git a/src/tm/icons/Plus24.gif b/src/tm/icons/Plus24.gif new file mode 100644 index 0000000..d67ef28 Binary files /dev/null and b/src/tm/icons/Plus24.gif differ diff --git a/src/tm/icons/Redo24.gif b/src/tm/icons/Redo24.gif new file mode 100644 index 0000000..22f40b3 Binary files /dev/null and b/src/tm/icons/Redo24.gif differ diff --git a/src/tm/icons/Rewind24.gif b/src/tm/icons/Rewind24.gif new file mode 100644 index 0000000..4e77dde Binary files /dev/null and b/src/tm/icons/Rewind24.gif differ diff --git a/src/tm/icons/RotateLeft24.gif b/src/tm/icons/RotateLeft24.gif new file mode 100644 index 0000000..08f80d3 Binary files /dev/null and b/src/tm/icons/RotateLeft24.gif differ diff --git a/src/tm/icons/RotateRight24.gif b/src/tm/icons/RotateRight24.gif new file mode 100644 index 0000000..5fae73d Binary files /dev/null and b/src/tm/icons/RotateRight24.gif differ diff --git a/src/tm/icons/RowDelete24.gif b/src/tm/icons/RowDelete24.gif new file mode 100644 index 0000000..8cf2438 Binary files /dev/null and b/src/tm/icons/RowDelete24.gif differ diff --git a/src/tm/icons/RowInsertAfter24.gif b/src/tm/icons/RowInsertAfter24.gif new file mode 100644 index 0000000..0e32f21 Binary files /dev/null and b/src/tm/icons/RowInsertAfter24.gif differ diff --git a/src/tm/icons/Save24.gif b/src/tm/icons/Save24.gif new file mode 100644 index 0000000..bfa98a8 Binary files /dev/null and b/src/tm/icons/Save24.gif differ diff --git a/src/tm/icons/SaveAll24.gif b/src/tm/icons/SaveAll24.gif new file mode 100644 index 0000000..9e4e168 Binary files /dev/null and b/src/tm/icons/SaveAll24.gif differ diff --git a/src/tm/icons/SaveAs24.gif b/src/tm/icons/SaveAs24.gif new file mode 100644 index 0000000..97eb6fa Binary files /dev/null and b/src/tm/icons/SaveAs24.gif differ diff --git a/src/tm/icons/Selection24.gif b/src/tm/icons/Selection24.gif new file mode 100644 index 0000000..fef12de Binary files /dev/null and b/src/tm/icons/Selection24.gif differ diff --git a/src/tm/icons/ShiftDown24.gif b/src/tm/icons/ShiftDown24.gif new file mode 100644 index 0000000..673b9c3 Binary files /dev/null and b/src/tm/icons/ShiftDown24.gif differ diff --git a/src/tm/icons/ShiftLeft24.gif b/src/tm/icons/ShiftLeft24.gif new file mode 100644 index 0000000..2bf111e Binary files /dev/null and b/src/tm/icons/ShiftLeft24.gif differ diff --git a/src/tm/icons/ShiftRight24.gif b/src/tm/icons/ShiftRight24.gif new file mode 100644 index 0000000..5c92cc4 Binary files /dev/null and b/src/tm/icons/ShiftRight24.gif differ diff --git a/src/tm/icons/ShiftUp24.gif b/src/tm/icons/ShiftUp24.gif new file mode 100644 index 0000000..7a1a251 Binary files /dev/null and b/src/tm/icons/ShiftUp24.gif differ diff --git a/src/tm/icons/StepBack24.gif b/src/tm/icons/StepBack24.gif new file mode 100644 index 0000000..124bac7 Binary files /dev/null and b/src/tm/icons/StepBack24.gif differ diff --git a/src/tm/icons/StepForward24.gif b/src/tm/icons/StepForward24.gif new file mode 100644 index 0000000..31a246f Binary files /dev/null and b/src/tm/icons/StepForward24.gif differ diff --git a/src/tm/icons/Swap24.gif b/src/tm/icons/Swap24.gif new file mode 100644 index 0000000..04b1d09 Binary files /dev/null and b/src/tm/icons/Swap24.gif differ diff --git a/src/tm/icons/TMIcon.gif b/src/tm/icons/TMIcon.gif new file mode 100644 index 0000000..aab1bab Binary files /dev/null and b/src/tm/icons/TMIcon.gif differ diff --git a/src/tm/icons/Undo24.gif b/src/tm/icons/Undo24.gif new file mode 100644 index 0000000..1d545a7 Binary files /dev/null and b/src/tm/icons/Undo24.gif differ diff --git a/src/tm/icons/Up24.gif b/src/tm/icons/Up24.gif new file mode 100644 index 0000000..3db8873 Binary files /dev/null and b/src/tm/icons/Up24.gif differ diff --git a/src/tm/icons/Zoom24.gif b/src/tm/icons/Zoom24.gif new file mode 100644 index 0000000..86ae863 Binary files /dev/null and b/src/tm/icons/Zoom24.gif differ diff --git a/src/tm/icons/ZoomCursor24.gif b/src/tm/icons/ZoomCursor24.gif new file mode 100644 index 0000000..027ce45 Binary files /dev/null and b/src/tm/icons/ZoomCursor24.gif differ diff --git a/src/tm/icons/ZoomIn24.gif b/src/tm/icons/ZoomIn24.gif new file mode 100644 index 0000000..dbd4477 Binary files /dev/null and b/src/tm/icons/ZoomIn24.gif differ diff --git a/src/tm/icons/ZoomOut24.gif b/src/tm/icons/ZoomOut24.gif new file mode 100644 index 0000000..259bf9c Binary files /dev/null and b/src/tm/icons/ZoomOut24.gif differ diff --git a/src/tm/modaldialog/TMAddToTreeDialog.java b/src/tm/modaldialog/TMAddToTreeDialog.java new file mode 100644 index 0000000..344c321 --- /dev/null +++ b/src/tm/modaldialog/TMAddToTreeDialog.java @@ -0,0 +1,195 @@ +/* +* +* 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.modaldialog; + +import tm.treenodes.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.tree.*; +import java.awt.*; +import java.awt.event.*; + +/** +* +* +**/ + +public class TMAddToTreeDialog extends TMModalDialog { + + private TMTreeNodeTree tree; + private JLabel descLabel; + private JLabel createInLabel; + private JTextField descField; + private JButton newFolderButton; + private TMNewFolderDialog newFolderDialog; + private JScrollPane scrollPane; + +/** +* +* Creates the dialog. +* +**/ + + public TMAddToTreeDialog(Frame owner, String title, tm.utils.Xlator xl) { + super(owner, title, xl); + newFolderDialog = new TMNewFolderDialog(owner, xl); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + descLabel = new JLabel(xlate("Description_Prompt")); + descField = new JTextField(); + descField.getDocument().addDocumentListener(new TMDocumentListener()); + createInLabel = new JLabel(xlate("Create_In")); + + newFolderButton = new JButton(xlate("New_Folder")); + newFolderButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doNewFolderCommand(); + } + } + ); + + tree = new TMTreeNodeTree(); + tree.setCellRenderer(new FolderCellRenderer()); + scrollPane = new JScrollPane(tree); + + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + buildConstraints(gbc, 0, 0, 1, 1, 25, 10); + gbl.setConstraints(descLabel, gbc); + p.add(descLabel); + gbc.fill = GridBagConstraints.HORIZONTAL; + buildConstraints(gbc, 1, 0, 1, 1, 75, 10); + gbl.setConstraints(descField, gbc); + p.add(descField); + gbc.fill = GridBagConstraints.NONE; + buildConstraints(gbc, 0, 1, 1, 1, 25, 10); + gbl.setConstraints(createInLabel, gbc); + p.add(createInLabel); + gbc.anchor = GridBagConstraints.EAST; + buildConstraints(gbc, 1, 1, 1, 1, 75, 10); + gbl.setConstraints(newFolderButton, gbc); + p.add(newFolderButton); + gbc.anchor = GridBagConstraints.CENTER; + gbc.fill = GridBagConstraints.BOTH; + buildConstraints(gbc, 0, 2, 2, 1, 80, 80); + gbl.setConstraints(scrollPane, gbc); + p.add(scrollPane); + + p.setPreferredSize(new Dimension(300, 200)); + return p; + } + +/** +* +* Prompts the user for a folder name, then inserts a folder node with that name +* as a subfolder of the currently selected node in the tree. +* +**/ + + public void doNewFolderCommand() { + newFolderDialog.setName(""); // clear previous input, if any + int retVal = newFolderDialog.showDialog(); // prompt user for folder name + if (retVal == JOptionPane.OK_OPTION) { + // create & insert the folder + String name = newFolderDialog.getName(); + FolderNode folder = (FolderNode)tree.getSelectedNode(); + FolderNode newFolder = new FolderNode(name); + folder.add(newFolder); + ((DefaultTreeModel)tree.getModel()).reload(folder); + tree.expandNode(newFolder); + tree.setSelectionPath(new TreePath(newFolder.getPath())); + scrollPane.revalidate(); + } + } + +/** +* +* Gets the folder where the new item is to be inserted. +* +**/ + + public FolderNode getFolder() { + return (FolderNode)tree.getSelectedNode(); + } + +/** +* +* Gets the description that the user entered for the item. +* +**/ + + public String getDescription() { + return descField.getText(); + } + +/** +* +* +* +**/ + + private class FolderCellRenderer extends DefaultTreeCellRenderer { + + public FolderCellRenderer() { + super(); + setLeafIcon(openIcon); + } + } + +/** +* +* +* +**/ + + public int showDialog(TMTreeNode root) { + tree.loadTreeNodes(root, false); + descField.setText(""); + maybeEnableOKButton(); + SwingUtilities.invokeLater( new Runnable() { + public void run() { + descField.requestFocus(); + } + }); + return super.showDialog(); + } + +/** +* +* +* +**/ + + public boolean inputOK() { + return !(descField.getText().trim().equals("")); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMBlockSizeDialog.java b/src/tm/modaldialog/TMBlockSizeDialog.java new file mode 100644 index 0000000..df1455c --- /dev/null +++ b/src/tm/modaldialog/TMBlockSizeDialog.java @@ -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.modaldialog; + +import tm.utils.DecimalNumberVerifier; + +import javax.swing.*; +import java.awt.*; + +/** +* +* The dialog where user can enter new dimensions for the block. +* +**/ + +public class TMBlockSizeDialog extends TMModalDialog { + + private JLabel colsLabel; + private JLabel rowsLabel; + private JTextField colsField; + private JTextField rowsField; + +/** +* +* Creates the block size dialog. +* +**/ + + public TMBlockSizeDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "Block_Size_Dialog_Title", xl); + } + +/** +* +* +* +**/ + + public int getCols() { + return Integer.parseInt(colsField.getText()); + } + +/** +* +* +* +**/ + + public int getRows() { + return Integer.parseInt(rowsField.getText()); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + colsLabel = new JLabel(xlate("Columns_Prompt")); + rowsLabel = new JLabel(xlate("Rows_Prompt")); + colsField = new JTextField(); + rowsField = new JTextField(); + + JPanel colsPane = new JPanel(); + colsPane.setLayout(new BoxLayout(colsPane, BoxLayout.X_AXIS)); + colsPane.add(colsLabel); + colsPane.add(colsField); + + JPanel rowsPane = new JPanel(); + rowsPane.setLayout(new BoxLayout(rowsPane, BoxLayout.X_AXIS)); + rowsPane.add(rowsLabel); + rowsPane.add(rowsField); + + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + + buildConstraints(gbc, 0, 0, 1, 1, 100, 50); + gbl.setConstraints(colsPane, gbc); + p.add(colsPane); + + buildConstraints(gbc, 0, 1, 1, 1, 100, 50); + gbl.setConstraints(rowsPane, gbc); + p.add(rowsPane); + + p.setPreferredSize(new Dimension(200, 60)); + + colsField.setColumns(2); + rowsField.setColumns(2); + + colsField.addKeyListener(new DecimalNumberVerifier()); + colsField.getDocument().addDocumentListener(new TMDocumentListener()); + rowsField.addKeyListener(new DecimalNumberVerifier()); + rowsField.getDocument().addDocumentListener(new TMDocumentListener()); + + return p; + } + + public int showDialog(int initialCols, int initialRows) { + colsField.setText(Integer.toString(initialCols)); + rowsField.setText(Integer.toString(initialRows)); + maybeEnableOKButton(); + SwingUtilities.invokeLater( new Runnable() { + public void run() { + colsField.requestFocus(); + } + }); + return super.showDialog(); + } + + public boolean inputOK() { + return (!colsField.getText().equals("") && !rowsField.getText().equals("") + && (getCols() > 0) && (getRows() > 0)); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMCanvasSizeDialog.java b/src/tm/modaldialog/TMCanvasSizeDialog.java new file mode 100644 index 0000000..c98e4e8 --- /dev/null +++ b/src/tm/modaldialog/TMCanvasSizeDialog.java @@ -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.modaldialog; + +import tm.utils.DecimalNumberVerifier; + +import javax.swing.*; +import java.awt.*; + +/** +* +* The dialog where user can enter new dimensions for the current view. +* +**/ + +public class TMCanvasSizeDialog extends TMModalDialog { + + private JLabel colsLabel; + private JLabel rowsLabel; + private JTextField colsField; + private JTextField rowsField; + +/** +* +* Creates the canvas size dialog. +* +**/ + + public TMCanvasSizeDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "Canvas_Size_Dialog_Title", xl); + } + +/** +* +* +* +**/ + + public int getCols() { + return Integer.parseInt(colsField.getText()); + } + +/** +* +* +* +**/ + + public int getRows() { + return Integer.parseInt(rowsField.getText()); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + colsLabel = new JLabel(xlate("Columns_Prompt")); + rowsLabel = new JLabel(xlate("Rows_Prompt")); + colsField = new JTextField(); + rowsField = new JTextField(); + + JPanel colsPane = new JPanel(); + colsPane.setLayout(new BoxLayout(colsPane, BoxLayout.X_AXIS)); + colsPane.add(colsLabel); + colsPane.add(colsField); + + JPanel rowsPane = new JPanel(); + rowsPane.setLayout(new BoxLayout(rowsPane, BoxLayout.X_AXIS)); + rowsPane.add(rowsLabel); + rowsPane.add(rowsField); + + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + + buildConstraints(gbc, 0, 0, 1, 1, 100, 50); + gbl.setConstraints(colsPane, gbc); + p.add(colsPane); + + buildConstraints(gbc, 0, 1, 1, 1, 100, 50); + gbl.setConstraints(rowsPane, gbc); + p.add(rowsPane); + + p.setPreferredSize(new Dimension(200, 60)); + + colsField.setColumns(3); + rowsField.setColumns(3); + + colsField.addKeyListener(new DecimalNumberVerifier()); + colsField.getDocument().addDocumentListener(new TMDocumentListener()); + rowsField.addKeyListener(new DecimalNumberVerifier()); + rowsField.getDocument().addDocumentListener(new TMDocumentListener()); + + return p; + } + + public int showDialog(int initialCols, int initialRows) { + colsField.setText(Integer.toString(initialCols)); + rowsField.setText(Integer.toString(initialRows)); + maybeEnableOKButton(); + SwingUtilities.invokeLater( new Runnable() { + public void run() { + colsField.requestFocus(); + } + }); + return super.showDialog(); + } + + public boolean inputOK() { + return (!colsField.getText().equals("") && !rowsField.getText().equals("") + && (getCols() > 0) && (getRows() > 0)); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMCustomCodecDialog.java b/src/tm/modaldialog/TMCustomCodecDialog.java new file mode 100644 index 0000000..fe7cda8 --- /dev/null +++ b/src/tm/modaldialog/TMCustomCodecDialog.java @@ -0,0 +1,196 @@ +/* +* +* 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.modaldialog; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +/** +* +* The dialog that's shown when user wants to define a new codec. +* +**/ + +public class TMCustomCodecDialog extends JDialog { + + int result; + + // labels + private JLabel bppLabel = new JLabel("Bits per pixel:"); + private JLabel rmaskLabel = new JLabel("Red bitmask:"); + private JLabel gmaskLabel = new JLabel("Green bitmask:"); + private JLabel bmaskLabel = new JLabel("Blue bitmask:"); + private JLabel amaskLabel = new JLabel("Alpha bitmask:"); + private JLabel endiannessLabel = new JLabel("Endianness:"); + + // input fields + private JFormattedTextField bppField = new JFormattedTextField(); + private JFormattedTextField rmaskField = new JFormattedTextField(); + private JFormattedTextField gmaskField = new JFormattedTextField(); + private JFormattedTextField bmaskField = new JFormattedTextField(); + private JFormattedTextField amaskField = new JFormattedTextField(); + + private JButton okButton = new JButton("OK"); + private JButton cancelButton = new JButton("Cancel"); + +/** +* +* Creates the custom codec dialog. +* +**/ + + public TMCustomCodecDialog(Frame owner, String title, boolean modal) { + super(owner, title, modal); + JPanel pane = new JPanel(); + setContentPane(pane); + pane.setLayout(new GridLayout(6, 2)); + pane.add(bppLabel); + pane.add(bppField); + pane.add(rmaskLabel); + pane.add(rmaskField); + pane.add(gmaskLabel); + pane.add(gmaskField); + pane.add(bmaskLabel); + pane.add(bmaskField); + pane.add(amaskLabel); + pane.add(amaskField); + pane.add(okButton); + pane.add(cancelButton); + + okButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + okClicked(); + } + } + ); + cancelButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + cancelClicked(); + } + } + ); + } + +/** +* +* +* +**/ + + public int getBitsPerPixel() { + return 0; + } + +/** +* +* +* +**/ + + public int getRedMask() { + return 0; + } + +/** +* +* +* +**/ + + public int getGreenMask() { + return 0; + } + +/** +* +* +* +**/ + + public int getBlueMask() { + return 0; + } + +/** +* +* +* +**/ + + public int getAlphaMask() { + return 0; + } + +/** +* +* +* +**/ + + public int getEndianness() { + return 0; + } + +/** +* +* +* +**/ + + public String getDescription() { + return ""; + } + +/** +* +* +* +**/ + + public void okClicked() { + result = JOptionPane.OK_OPTION; + setVisible(false); + } + +/** +* +* +* +**/ + + public void cancelClicked() { + setVisible(false); + } + +/** +* +* +* +**/ + + public int showDialog() { + result = JOptionPane.CANCEL_OPTION; + setVisible(true); + return result; + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMGoToDialog.java b/src/tm/modaldialog/TMGoToDialog.java new file mode 100644 index 0000000..d6116cf --- /dev/null +++ b/src/tm/modaldialog/TMGoToDialog.java @@ -0,0 +1,197 @@ +/* +* +* 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.modaldialog; + +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.border.EtchedBorder; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.event.*; + +/** +* +* The dialog where user can enter desired file offset. +* +**/ + +public class TMGoToDialog extends TMModalDialog { + + // available modes + public static int ABSOLUTE_MODE = 1; + public static int RELATIVE_MODE = 2; + + private JTextField ofsField; + private JRadioButton hexButton; + private JRadioButton decButton; + private JRadioButton absButton; + private JRadioButton relButton; + +/** +* +* Creates the goto dialog. +* +**/ + + public TMGoToDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "Go_To_Dialog_Title", xl); + } + +/** +* +* Gets the selected mode. +* +**/ + + public int getMode() { + return (absButton.isSelected()) ? ABSOLUTE_MODE : RELATIVE_MODE; + } + +/** +* +* Gets the offset that was entered. +* +**/ + + public int getOffset() { + if (inputOK()) + return Integer.parseInt(ofsField.getText(), getRadix()); + return 0; + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + + JPanel ofsPane = new JPanel(); + ofsPane.setBorder(new TitledBorder(new EtchedBorder(), xlate("Offset"))); + ofsField = new JTextField(); + ofsPane.add(ofsField); + + JPanel radixPane = new JPanel(); + radixPane.setBorder(new TitledBorder(new EtchedBorder(), xlate("Radix"))); + radixPane.setLayout(new BoxLayout(radixPane, BoxLayout.Y_AXIS)); + hexButton = new JRadioButton(xlate("Hex")); + decButton = new JRadioButton(xlate("Dec")); + radixPane.add(hexButton); + radixPane.add(decButton); + + JPanel modePane = new JPanel(); + modePane.setBorder(new TitledBorder(new EtchedBorder(), xlate("Mode"))); + modePane.setLayout(new BoxLayout(modePane, BoxLayout.Y_AXIS)); + absButton = new JRadioButton(xlate("Absolute")); + relButton = new JRadioButton(xlate("Relative")); + modePane.add(absButton); + modePane.add(relButton); + + buildConstraints(gbc, 0, 0, 1, 1, 50, 100); + gbl.setConstraints(ofsPane, gbc); + p.add(ofsPane); + + buildConstraints(gbc, 1, 0, 1, 1, 25, 100); + gbl.setConstraints(radixPane, gbc); + p.add(radixPane); + + buildConstraints(gbc, 2, 0, 1, 1, 25, 100); + gbl.setConstraints(modePane, gbc); + p.add(modePane); + + ButtonGroup modeButtonGroup = new ButtonGroup(); + modeButtonGroup.add(absButton); + modeButtonGroup.add(relButton); + absButton.setSelected(true); + + ButtonGroup radixButtonGroup = new ButtonGroup(); + radixButtonGroup.add(hexButton); + radixButtonGroup.add(decButton); + hexButton.setSelected(true); + + p.setPreferredSize(new Dimension(300, 100)); + + ofsField.setText(""); + ofsField.setColumns(10); + ofsField.addKeyListener(new KeyAdapter() { + public void keyTyped(KeyEvent e) { + char c = e.getKeyChar(); + if (!((Character.digit(c, getRadix()) != -1) || + (c == KeyEvent.VK_BACK_SPACE) || + (c == KeyEvent.VK_DELETE))) { + getToolkit().beep(); + e.consume(); + } + } + }); + + ofsField.getDocument().addDocumentListener(new TMDocumentListener()); + + hexButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (inputOK()) { + int ofs = Integer.parseInt(ofsField.getText(), 10); + ofsField.setText(Integer.toString(ofs, 16)); + } + } + }); + decButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (inputOK()) { + int ofs = Integer.parseInt(ofsField.getText(), 16); + ofsField.setText(Integer.toString(ofs, 10)); + } + } + }); + + return p; + } + +/** +* +* +* +**/ + + public int showDialog() { + ofsField.setText(""); + maybeEnableOKButton(); + SwingUtilities.invokeLater( new Runnable() { + public void run() { + ofsField.requestFocus(); + } + }); + return super.showDialog(); + } + + private int getRadix() { + return (hexButton.isSelected()) ? 16 : 10; + } + + public boolean inputOK() { + return !(ofsField.getText().equals("")); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMImportInternalPaletteDialog.java b/src/tm/modaldialog/TMImportInternalPaletteDialog.java new file mode 100644 index 0000000..41cbe38 --- /dev/null +++ b/src/tm/modaldialog/TMImportInternalPaletteDialog.java @@ -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.modaldialog; + +import tm.colorcodecs.ColorCodec; +import tm.utils.DecimalNumberVerifier; +import tm.utils.HexadecimalNumberVerifier; + +import javax.swing.*; +import javax.swing.border.*; +import java.awt.*; +import java.util.Vector; + +/** +* +* +* +**/ + +public class TMImportInternalPaletteDialog extends TMModalDialog { + + private JLabel offsetLabel; + private JTextField offsetField; + private JLabel sizeLabel; + private JTextField sizeField; + private JLabel formatLabel; + private JComboBox codecCombo; + private JRadioButton littleRadio; + private JRadioButton bigRadio; + private JCheckBox copyCheck; + +/** +* +* Creates the dialog. +* +**/ + + public TMImportInternalPaletteDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "Import_Internal_Palette_Dialog_Title", xl); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + offsetLabel = new JLabel(xlate("Offset_Prompt")); + offsetField = new JTextField(); + sizeLabel = new JLabel(xlate("Size_Prompt")); + sizeField = new JTextField(); + formatLabel = new JLabel(xlate("Format")); + codecCombo = new JComboBox(); + copyCheck = new JCheckBox(xlate("Copy")); + + offsetField.setColumns(8); + sizeField.setColumns(4); + offsetField.getDocument().addDocumentListener(new TMDocumentListener()); + offsetField.addKeyListener(new HexadecimalNumberVerifier()); + sizeField.getDocument().addDocumentListener(new TMDocumentListener()); + sizeField.addKeyListener(new DecimalNumberVerifier()); + + JPanel endiannessPane = new JPanel(); + endiannessPane.setBorder(new TitledBorder(new EtchedBorder(), xlate("Endianness"))); + endiannessPane.setLayout(new BoxLayout(endiannessPane, BoxLayout.Y_AXIS)); + littleRadio = new JRadioButton(xlate("Little_Endian")+" "); + bigRadio = new JRadioButton(xlate("Big_Endian")+" "); + endiannessPane.add(littleRadio); + endiannessPane.add(bigRadio); + + ButtonGroup endiannessButtonGroup = new ButtonGroup(); + endiannessButtonGroup.add(littleRadio); + endiannessButtonGroup.add(bigRadio); + littleRadio.setSelected(true); + + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + buildConstraints(gbc, 0, 0, 1, 1, 20, 10); + gbl.setConstraints(offsetLabel, gbc); + p.add(offsetLabel); + buildConstraints(gbc, 1, 0, 1, 1, 30, 10); + gbl.setConstraints(offsetField, gbc); + p.add(offsetField); + buildConstraints(gbc, 2, 0, 1, 1, 20, 10); + gbl.setConstraints(sizeLabel, gbc); + p.add(sizeLabel); + buildConstraints(gbc, 3, 0, 1, 1, 30, 10); + gbl.setConstraints(sizeField, gbc); + p.add(sizeField); + buildConstraints(gbc, 0, 1, 1, 1, 20, 10); + gbl.setConstraints(formatLabel, gbc); + p.add(formatLabel); + buildConstraints(gbc, 1, 1, 2, 1, 55, 10); + gbl.setConstraints(codecCombo, gbc); + p.add(codecCombo); + buildConstraints(gbc, 3, 1, 1, 1, 25, 10); + gbl.setConstraints(endiannessPane, gbc); + p.add(endiannessPane); + buildConstraints(gbc, 0, 2, 1, 1, 25, 10); + gbl.setConstraints(copyCheck, gbc); + p.add(copyCheck); + + p.setPreferredSize(new Dimension(450, 150)); + + return p; + } + + public int getOffset() { + return Integer.parseInt(offsetField.getText(), 16); + } + + public int getPaletteSize() { + return Integer.parseInt(sizeField.getText()); + } + + public int getEndianness() { + return littleRadio.isSelected() ? ColorCodec.LITTLE_ENDIAN : ColorCodec.BIG_ENDIAN; + } + + public ColorCodec getCodec() { + return (ColorCodec)codecCombo.getSelectedItem(); + } + + public boolean getCopy() { + return copyCheck.isSelected(); + } + +/** +* +* +* +**/ + + public void setCodecs(Vector codecs) { + codecCombo.removeAllItems(); + for (int i=0; i 0)); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMModalDialog.java b/src/tm/modaldialog/TMModalDialog.java new file mode 100644 index 0000000..287da44 --- /dev/null +++ b/src/tm/modaldialog/TMModalDialog.java @@ -0,0 +1,203 @@ +/* +* +* 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.modaldialog; + +import tm.utils.Xlator; +import javax.swing.*; +import javax.swing.event.*; +import java.awt.*; +import java.awt.event.*; + +/** +* +* A class providing a general framework for modal dialogs. +* It has an OK and Cancel button. Must be subclassed to +* provide the getDialogPane() method, which creates and +* returns a panel with the actual dialog components where +* input can be given by the user. +* +**/ + +public abstract class TMModalDialog extends JDialog { + + private int result; + private JButton okButton; + private JButton cancelButton; + private Xlator xl; + private JPanel dialogPane; + +/** +* +* +* +**/ + + public TMModalDialog(Frame owner, String title, Xlator xl) { + super(owner, xl != null ? xl.xlate(title) : title, true); + this.xl = xl; + setResizable(false); + okButton = new JButton(xlate("OK")); + cancelButton = new JButton(xlate("Cancel")); + okButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + approveInput(); + } + } + ); + cancelButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + cancelInput(); + } + } + ); + JPanel buttonPane = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + buttonPane.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.EAST; + buildConstraints(gbc, 0, 0, 1, 1, 45, 100); + gbl.setConstraints(okButton, gbc); + buttonPane.add(okButton); + JLabel filler = new JLabel(); + gbc.anchor = GridBagConstraints.CENTER; + buildConstraints(gbc, 1, 0, 1, 1, 10, 100); + gbl.setConstraints(filler, gbc); + buttonPane.add(filler); + buildConstraints(gbc, 2, 0, 1, 1, 45, 100); + gbc.anchor = GridBagConstraints.WEST; + gbl.setConstraints(cancelButton, gbc); + buttonPane.add(cancelButton); + + JPanel contentPane = new JPanel() { + public Insets getInsets() { + return new Insets(10,10,10,10); + } + }; + setContentPane(contentPane); + contentPane.setLayout(new BorderLayout()); + contentPane.add(buttonPane, BorderLayout.SOUTH); + this.dialogPane = getDialogPane(); + contentPane.add(this.dialogPane, BorderLayout.CENTER); + pack(); + + } + +/** +* +* Shows the dialog. +* +**/ + + public int showDialog() { + // center the dialog + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int insetx = (screenSize.width - getWidth()) / 2; + int insety = (screenSize.height - getHeight()) / 2; + setBounds(insetx, insety, + getWidth(), getHeight()); + + result = JOptionPane.CANCEL_OPTION; + setVisible(true); + return result; + } + +/** +* +* +* +**/ + + protected void approveInput() { + result = JOptionPane.OK_OPTION; + setVisible(false); + } + +/** +* +* +* +**/ + + protected void cancelInput() { + result = JOptionPane.CANCEL_OPTION; + setVisible(false); + } + +/** +* +* Method that provides the real content pane of the dialog. +* +**/ + + protected abstract JPanel getDialogPane(); + +/** +* +* +* +**/ + + protected static void buildConstraints(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int wx, int wy) { + gbc.gridx = gx; + gbc.gridy = gy; + gbc.gridwidth = gw; + gbc.gridheight = gh; + gbc.weightx = wx; + gbc.weighty = wy; + } + +/** +* +* Sets enabled state of OK button. +* Subclasses can use this to keep the user from OK'ing the input when it +* isn't valid/completed. +* +**/ + + public void maybeEnableOKButton() { + okButton.setEnabled(inputOK()); + } + + public abstract boolean inputOK(); + + protected class TMDocumentListener implements DocumentListener { + public void changedUpdate(DocumentEvent e) { + maybeEnableOKButton(); + } + public void insertUpdate(DocumentEvent e) { + maybeEnableOKButton(); + } + public void removeUpdate(DocumentEvent e) { + maybeEnableOKButton(); + } + } + + public String xlate(String key) { + try { + String value = xl.xlate(key); + return value; + } + catch (Exception e) { + return key; + } + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMNewFileDialog.java b/src/tm/modaldialog/TMNewFileDialog.java new file mode 100644 index 0000000..127e207 --- /dev/null +++ b/src/tm/modaldialog/TMNewFileDialog.java @@ -0,0 +1,101 @@ +/* +* +* 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.modaldialog; + +import tm.utils.DecimalNumberVerifier; + +import javax.swing.*; +import java.awt.*; + +/** +* +* The dialog that's shown when user wants to create a new ("blank") file. +* +**/ + +public class TMNewFileDialog extends TMModalDialog { + + private JLabel sizeLabel; + private JTextField sizeField; + +/** +* +* Creates the New File dialog. +* +**/ + + public TMNewFileDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "New_File_Dialog_Title", xl); + } + +/** +* +* Gets the filesize given by the user. +* +**/ + + public int getFileSize() { + return Integer.parseInt(sizeField.getText()); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + sizeLabel = new JLabel(xlate("Size_Prompt")); + buildConstraints(gbc, 0, 0, 1, 1, 50, 100); + gbl.setConstraints(sizeLabel, gbc); + p.add(sizeLabel); + sizeField = new JTextField(); + buildConstraints(gbc, 1, 0, 1, 1, 50, 100); + gbl.setConstraints(sizeField, gbc); + p.add(sizeField); + p.setPreferredSize(new Dimension(200, 50)); + + sizeField.setColumns(7); + sizeField.addKeyListener(new DecimalNumberVerifier()); + sizeField.getDocument().addDocumentListener(new TMDocumentListener()); + + return p; + } + + public int showDialog() { + sizeField.setText(""); + maybeEnableOKButton(); + SwingUtilities.invokeLater( new Runnable() { + public void run() { + sizeField.requestFocus(); + } + }); + return super.showDialog(); + } + + public boolean inputOK() { + return !(sizeField.getText().equals("") || (getFileSize() == 0)); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMNewFolderDialog.java b/src/tm/modaldialog/TMNewFolderDialog.java new file mode 100644 index 0000000..ff81b18 --- /dev/null +++ b/src/tm/modaldialog/TMNewFolderDialog.java @@ -0,0 +1,106 @@ +/* +* +* 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.modaldialog; + +import javax.swing.*; +import javax.swing.event.*; +import java.awt.*; + +/** +* +* The dialog that's shown when user wants to create a new folder. +* +**/ + +public class TMNewFolderDialog extends TMModalDialog { + + private JLabel nameLabel; + private JTextField nameField; + +/** +* +* Creates the New Folder dialog. +* +**/ + + public TMNewFolderDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "New_Folder_Dialog_Title", xl); + } + +/** +* +* Gets the folder name given by the user. +* +**/ + + public String getName() { + return nameField.getText(); + } + +/** +* +* +* +**/ + + public void setName(String name) { + nameField.setText(name); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + JPanel p = new JPanel(); + nameLabel = new JLabel(xlate("Folder_Name_Prompt")); + p.add(nameLabel); + nameField = new JTextField(); + nameField.getDocument().addDocumentListener(new TMDocumentListener()); + nameField.setColumns(15); + p.add(nameField); + p.setPreferredSize(new Dimension(300, 50)); + + return p; + } + +/** +* +* +* +**/ + + public int showDialog() { + nameField.setText(""); + maybeEnableOKButton(); + SwingUtilities.invokeLater( new Runnable() { + public void run() { + nameField.requestFocus(); + } + }); + return super.showDialog(); + } + + public boolean inputOK() { + return !(nameField.getText().trim().equals("")); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMNewPaletteDialog.java b/src/tm/modaldialog/TMNewPaletteDialog.java new file mode 100644 index 0000000..ef5587d --- /dev/null +++ b/src/tm/modaldialog/TMNewPaletteDialog.java @@ -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.modaldialog; + +import tm.colorcodecs.ColorCodec; +import tm.utils.DecimalNumberVerifier; + +import javax.swing.*; +import javax.swing.border.*; +import java.awt.*; +import java.util.Vector; + +/** +* +* +**/ + +public class TMNewPaletteDialog extends TMModalDialog { + + private JLabel sizeLabel; + private JFormattedTextField sizeField; + private JLabel formatLabel; + private JComboBox codecCombo; + private JRadioButton littleRadio; + private JRadioButton bigRadio; + +/** +* +* Creates the dialog. +* +**/ + + public TMNewPaletteDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "New_Palette_Dialog_Title", xl); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + sizeLabel = new JLabel(xlate("Size_Prompt")); + sizeField = new JFormattedTextField(); + formatLabel = new JLabel(xlate("Format")); + codecCombo = new JComboBox(); + + sizeField.setColumns(5); + sizeField.getDocument().addDocumentListener(new TMDocumentListener()); + sizeField.addKeyListener(new DecimalNumberVerifier()); + + JPanel endiannessPane = new JPanel(); + endiannessPane.setBorder(new TitledBorder(new EtchedBorder(), xlate("Endianness"))); + endiannessPane.setLayout(new BoxLayout(endiannessPane, BoxLayout.Y_AXIS)); + littleRadio = new JRadioButton(xlate("Little_Endian")+" "); + bigRadio = new JRadioButton(xlate("Big_Endian")+" "); + endiannessPane.add(littleRadio); + endiannessPane.add(bigRadio); + + ButtonGroup endiannessButtonGroup = new ButtonGroup(); + endiannessButtonGroup.add(littleRadio); + endiannessButtonGroup.add(bigRadio); + littleRadio.setSelected(true); + + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + buildConstraints(gbc, 0, 0, 1, 1, 25, 10); + gbl.setConstraints(sizeLabel, gbc); + p.add(sizeLabel); + buildConstraints(gbc, 1, 0, 1, 1, 25, 10); + gbl.setConstraints(sizeField, gbc); + p.add(sizeField); + buildConstraints(gbc, 0, 1, 1, 1, 25, 10); + gbl.setConstraints(formatLabel, gbc); + p.add(formatLabel); + buildConstraints(gbc, 1, 1, 2, 1, 50, 10); + gbl.setConstraints(codecCombo, gbc); + p.add(codecCombo); + buildConstraints(gbc, 3, 1, 1, 1, 25, 10); + gbl.setConstraints(endiannessPane, gbc); + p.add(endiannessPane); + + p.setPreferredSize(new Dimension(400, 100)); + + return p; + } + + public int getPaletteSize() { + return Integer.parseInt(sizeField.getText()); + } + + public int getEndianness() { + return littleRadio.isSelected() ? ColorCodec.LITTLE_ENDIAN : ColorCodec.BIG_ENDIAN; + } + + public ColorCodec getCodec() { + return (ColorCodec)codecCombo.getSelectedItem(); + } + +/** +* +* +* +**/ + + public void setCodecs(Vector codecs) { + codecCombo.removeAllItems(); + for (int i=0; i 0)); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMOrganizeTreeDialog.java b/src/tm/modaldialog/TMOrganizeTreeDialog.java new file mode 100644 index 0000000..90fe577 --- /dev/null +++ b/src/tm/modaldialog/TMOrganizeTreeDialog.java @@ -0,0 +1,362 @@ +/* +* +* 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.modaldialog; + +import tm.treenodes.*; +import tm.modaldialog.TMNewFolderDialog; +import tm.utils.Xlator; +import javax.swing.*; +import javax.swing.tree.*; +import javax.swing.event.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.dnd.*; +import java.awt.datatransfer.*; + +/** +* +* Dialog where the user can organize treenodes. +* Items and folders can be moved, renamed and deleted. +* New folders can be created. +* Items can be sorted (?) +* +**/ + +public class TMOrganizeTreeDialog extends JDialog implements TreeModelListener { + + private TMTreeNodeTree tree = new TMTreeNodeTree(); + private JScrollPane scrollPane = new JScrollPane(tree); + private TMNewFolderDialog newFolderDialog; + + private JButton newFolderButton; + private JButton renameButton; + private JButton moveButton; + private JButton deleteButton; + private JButton closeButton; + private Xlator xl; + +/** +* +* +* +**/ + + public TMOrganizeTreeDialog(Frame owner, String title, Xlator xl) { + super(owner, xl != null ? xl.xlate(title) : title, true); + this.xl = xl; + + newFolderButton = new JButton(xlate("New_Folder")); + renameButton = new JButton(xlate("Rename")); + moveButton = new JButton(xlate("Move")); + deleteButton = new JButton(xlate("Delete")); + closeButton = new JButton(xlate("Close")); + + newFolderDialog = new TMNewFolderDialog(owner, xl); + setSize(500, 400); + + tree.setRootVisible(false); + tree.setEditable(true); + + DragSource dragSource = DragSource.getDefaultDragSource() ; + dragSource.createDefaultDragGestureRecognizer( + tree, //DragSource + DnDConstants.ACTION_MOVE, //specifies valid actions + new MyDragGestureListener() //DragGestureListener + ); + new DropTarget(tree, DnDConstants.ACTION_MOVE, new MyDropTargetListener(), true); + + JPanel contentPane = new JPanel() { + public Insets getInsets() { + return new Insets(10,10,10,10); + } + }; + setContentPane(contentPane); + contentPane.setLayout(new BorderLayout()); + + // create panel with command buttons + JPanel topPane = new JPanel(); + topPane.setLayout(new GridLayout(1, 4)); + topPane.add(renameButton); + topPane.add(moveButton); + topPane.add(newFolderButton); + topPane.add(deleteButton); + + // create panel with Close button + JPanel bottomPane = new JPanel(); + bottomPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); + bottomPane.add(closeButton); + + contentPane.add(topPane, BorderLayout.NORTH); + contentPane.add(scrollPane, BorderLayout.CENTER); + contentPane.add(bottomPane, BorderLayout.SOUTH); + + // make button action handlers + newFolderButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doNewFolderCommand(); + } + } + ); + renameButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doRenameCommand(); + } + } + ); + moveButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doMoveCommand(); + } + } + ); + deleteButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doDeleteCommand(); + } + } + ); + closeButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doCloseCommand(); + } + } + ); + + tree.addKeyListener( + new KeyAdapter() { + public void keyTyped(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_DELETE) { + doDeleteCommand(); + } + } + } + ); + } + +/** +* +* +* +**/ + + public void doNewFolderCommand() { + newFolderDialog.setName(""); // clear previous input, if any + int retVal = newFolderDialog.showDialog(); // prompt user for folder name + if (retVal == JOptionPane.OK_OPTION) { + // create & insert the folder + String name = newFolderDialog.getName(); + TMTreeNode node = tree.getSelectedNode(); + FolderNode folder; + if (node instanceof FolderNode) { + folder = (FolderNode)node; + } + else { + folder = (FolderNode)node.getParent(); + } + FolderNode newFolder = new FolderNode(name); + folder.add(newFolder); + ((DefaultTreeModel)tree.getModel()).reload(folder); + tree.expandNode(newFolder); + tree.setSelectionPath(new TreePath(newFolder.getPath())); + scrollPane.revalidate(); + } + } + +/** +* +* +* +**/ + + public void doRenameCommand() { + DefaultMutableTreeNode node = tree.getSelectedNode(); + if (node.isRoot()) return; + tree.startEditingAtPath(tree.getSelectionPath()); + } + +/** +* +* +* +**/ + + public void doMoveCommand() { + JOptionPane.showMessageDialog( + this, + "Todo.\nUse drag and drop to move items.", + "Tile Molester", + JOptionPane.INFORMATION_MESSAGE + ); + } + +/** +* +* +* +**/ + + public void doDeleteCommand() { + TMTreeNode node = tree.getSelectedNode(); + if (node.isRoot()) return; + int retVal = JOptionPane.showConfirmDialog(this, + "You sure about this?", // i18n + "Tile Molester", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE); + if (retVal == JOptionPane.OK_OPTION) { + TMTreeNode parent = node.getTMParent(); + parent.remove(node); + + ((DefaultTreeModel)tree.getModel()).reload(parent); + tree.expandNode(parent); + tree.setSelectionPath(new TreePath(parent.getPath())); + scrollPane.revalidate(); + } + } + +/** +* +* +* +**/ + + public void doCloseCommand() { + setVisible(false); + } + +/** +* +* Shows the dialog. +* +**/ + + public void showDialog(FolderNode root) { + tree.loadTreeNodes(root, true); + tree.getModel().addTreeModelListener(this); + // center the dialog + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int insetx = (screenSize.width - getWidth()) / 2; + int insety = (screenSize.height - getHeight()) / 2; + setBounds(insetx, insety, + getWidth(), getHeight()); + + setVisible(true); + } + + public void treeNodesChanged(TreeModelEvent e) { + TMTreeNode node; + node = (TMTreeNode)e.getTreePath().getLastPathComponent(); + try { + int index = e.getChildIndices()[0]; + node = (TMTreeNode)node.getChildAt(index); + } catch (NullPointerException exc) {} + node.setText((String)node.getUserObject()); + } + + public void treeNodesInserted(TreeModelEvent e) { + } + + public void treeNodesRemoved(TreeModelEvent e) { + } + + public void treeStructureChanged(TreeModelEvent e) { + } + +/** +* +* +* +**/ + + public String xlate(String key) { + try { + String value = xl.xlate(key); + return value; + } + catch (Exception e) { + return key; + } + } + +/** +* +* +* +**/ + + private class MyDragGestureListener implements DragGestureListener { + + public void dragGestureRecognized(DragGestureEvent dge) { + TMTreeNode dragNode = tree.getSelectedNode(); + if ((dragNode != null) && !dragNode.isRoot()) { + dge.startDrag(DragSource.DefaultMoveDrop, new TMTreeNodeTransferable(dragNode)); + } + } + + } + +/** +* +* +* +**/ + + private class MyDropTargetListener extends DropTargetAdapter { + + public void drop(DropTargetDropEvent dtde) { + Transferable transferable = dtde.getTransferable(); + //flavor not supported, reject drop + if (!transferable.isDataFlavorSupported(TMTreeNodeTransferable.localTMTreeNodeFlavor)) { + dtde.rejectDrop(); + return; + } + TMTreeNode node = null; + try { + node = (TMTreeNode)transferable.getTransferData(TMTreeNodeTransferable.localTMTreeNodeFlavor); + } catch (Exception e) { } + TMTreeNode oldParent = node.getTMParent(); + Point loc = dtde.getLocation(); + TMTreeNode newParent; + try { + TreePath destinationPath = tree.getPathForLocation(loc.x, loc.y); + newParent = (TMTreeNode)destinationPath.getLastPathComponent(); + } + catch (Exception e) { + newParent = (TMTreeNode)((DefaultTreeModel)tree.getModel()).getRoot(); + } + if (!(newParent instanceof FolderNode)) { + newParent = newParent.getTMParent(); + } + oldParent.remove(node); + newParent.add(node); + ((DefaultTreeModel)tree.getModel()).reload(); + tree.expandNode(oldParent); + tree.expandNode(newParent); + tree.setSelectionPath(new TreePath(newParent.getPath())); + scrollPane.revalidate(); + } + + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMPaletteSizeDialog.java b/src/tm/modaldialog/TMPaletteSizeDialog.java new file mode 100644 index 0000000..71795cf --- /dev/null +++ b/src/tm/modaldialog/TMPaletteSizeDialog.java @@ -0,0 +1,101 @@ +/* +* +* 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.modaldialog; + +import tm.utils.DecimalNumberVerifier; + +import javax.swing.*; +import java.awt.*; + +/** +* +* The dialog that's shown when user wants to change the size of current palette. +* +**/ + +public class TMPaletteSizeDialog extends TMModalDialog { + + private JLabel sizeLabel; + private JTextField sizeField; + +/** +* +* Creates the Palette Size dialog. +* +**/ + + public TMPaletteSizeDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "Palette_Size_Dialog_Title", xl); + } + +/** +* +* Gets the palette size given by the user. +* +**/ + + public int getPaletteSize() { + return Integer.parseInt(sizeField.getText()); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + sizeLabel = new JLabel(xlate("Size_Prompt")); + buildConstraints(gbc, 0, 0, 1, 1, 50, 100); + gbl.setConstraints(sizeLabel, gbc); + p.add(sizeLabel); + sizeField = new JTextField(); + buildConstraints(gbc, 1, 0, 1, 1, 50, 100); + gbl.setConstraints(sizeField, gbc); + p.add(sizeField); + p.setPreferredSize(new Dimension(200, 50)); + + sizeField.setColumns(4); + sizeField.addKeyListener(new DecimalNumberVerifier()); + sizeField.getDocument().addDocumentListener(new TMDocumentListener()); + + return p; + } + + public int showDialog(int initialSize) { + sizeField.setText(Integer.toString(initialSize)); + maybeEnableOKButton(); + SwingUtilities.invokeLater( new Runnable() { + public void run() { + sizeField.requestFocus(); + } + }); + return super.showDialog(); + } + + public boolean inputOK() { + return !(sizeField.getText().equals("") || (getPaletteSize() == 0)); + } + +} \ No newline at end of file diff --git a/src/tm/modaldialog/TMStretchDialog.java b/src/tm/modaldialog/TMStretchDialog.java new file mode 100644 index 0000000..7b877dc --- /dev/null +++ b/src/tm/modaldialog/TMStretchDialog.java @@ -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.modaldialog; + +import tm.utils.DecimalNumberVerifier; + +import javax.swing.*; +import java.awt.*; + +/** +* +* The dialog where user can enter new dimensions for the current selection. +* +**/ + +public class TMStretchDialog extends TMModalDialog { + + private JLabel colsLabel; + private JLabel rowsLabel; + private JTextField colsField; + private JTextField rowsField; + +/** +* +* Creates the stretch dialog. +* +**/ + + public TMStretchDialog(Frame owner, tm.utils.Xlator xl) { + super(owner, "Stretch_Image_Dialog_Title", xl); + } + +/** +* +* +* +**/ + + public int getCols() { + return Integer.parseInt(colsField.getText()); + } + +/** +* +* +* +**/ + + public int getRows() { + return Integer.parseInt(rowsField.getText()); + } + +/** +* +* +* +**/ + + protected JPanel getDialogPane() { + colsLabel = new JLabel(xlate("Columns_Prompt")); + rowsLabel = new JLabel(xlate("Rows_Prompt")); + colsField = new JTextField(); + rowsField = new JTextField(); + + JPanel colsPane = new JPanel(); + colsPane.setLayout(new BoxLayout(colsPane, BoxLayout.X_AXIS)); + colsPane.add(colsLabel); + colsPane.add(colsField); + + JPanel rowsPane = new JPanel(); + rowsPane.setLayout(new BoxLayout(rowsPane, BoxLayout.X_AXIS)); + rowsPane.add(rowsLabel); + rowsPane.add(rowsField); + + JPanel p = new JPanel(); + GridBagLayout gbl = new GridBagLayout(); + p.setLayout(gbl); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + + buildConstraints(gbc, 0, 0, 1, 1, 100, 50); + gbl.setConstraints(colsPane, gbc); + p.add(colsPane); + + buildConstraints(gbc, 0, 1, 1, 1, 100, 50); + gbl.setConstraints(rowsPane, gbc); + p.add(rowsPane); + + p.setPreferredSize(new Dimension(200, 60)); + + colsField.setColumns(3); + rowsField.setColumns(3); + + colsField.addKeyListener(new DecimalNumberVerifier()); + colsField.getDocument().addDocumentListener(new TMDocumentListener()); + rowsField.addKeyListener(new DecimalNumberVerifier()); + rowsField.getDocument().addDocumentListener(new TMDocumentListener()); + + return p; + } + + public int showDialog(int initialCols, int initialRows) { + colsField.setText(Integer.toString(initialCols)); + rowsField.setText(Integer.toString(initialRows)); + maybeEnableOKButton(); + SwingUtilities.invokeLater( new Runnable() { + public void run() { + colsField.requestFocus(); + } + }); + return super.showDialog(); + } + + public boolean inputOK() { + return (!colsField.getText().equals("") && !rowsField.getText().equals("") + && (getCols() > 0) && (getRows() > 0)); + } + +} \ No newline at end of file diff --git a/src/tm/osbaldeston/image/BMP.java b/src/tm/osbaldeston/image/BMP.java new file mode 100644 index 0000000..6e7d925 --- /dev/null +++ b/src/tm/osbaldeston/image/BMP.java @@ -0,0 +1,500 @@ +package tm.osbaldeston.image; + +/** + * BMP - Wrapper Class for the loading & saving of uncompressed BMP files. + * + * Known problems/issues: + * Only 24 bit BMP files are output, images get converted to 24-bit + * which corresponds to Java's default colour model, output from + * PixelGrabber in Java 1.1.x. + * + * @see PCBinaryInputStream, PCBinaryOutputStream + * + * @author Richard J.Osbaldeston + * @version 1.1 02/08/98 + * @copyright Richard J.Osbaldeston (http://www.osbald.co.uk) + */ + +import java.awt.Image; +import java.awt.image.*; +import java.awt.Toolkit; +import java.io.*; +import java.net.URL; +import tm.osbaldeston.io.*; + +public class BMP { + + private java.awt.Image image; + private BmpFileheader bmp_fileheader = new BmpFileheader(); + private BmpInfoHeader bmp_infoheader = new BmpInfoHeader(); + private BmpPalette bmp_palette; + private int width; + private int height; + + /** + * Creates an BMP by reading from the given File. + * + * @param f File to read BMP from + */ + public BMP(File f) { + try { + PCBinaryInputStream file = new PCBinaryInputStream(f); + read(file); + } catch (IOException e) { + System.err.println(e); + } + } + + /** + * Creates an BMP by reading from the given File. + * + * @param url URL to read BMP from + */ + public BMP(URL url) { + try { + PCBinaryInputStream file = new PCBinaryInputStream(url); + read(file); + } catch (IOException e) { + System.err.println(e); + } + } + + /** + * Creates an BMP from the given Image, (for writing). + */ + public BMP(Image image) { + this.image=image; + width=image.getWidth(null); + height=image.getHeight(null); + bmp_infoheader.biWidth = (short)width; + bmp_infoheader.biHeight = (short)height; + bmp_infoheader.biBitCount = 24; + bmp_infoheader.biClrUsed = 0; + } + + /** + * Returns the image loaded, last saved image or null if unused. + * + * @return the current image + */ + public Image getImage() { + return image; + } + + /** + * Saves the BMP in a given file. + * + * @param f File to write BMP + */ + public void write(File f) { + try { + PCBinaryOutputStream file = new PCBinaryOutputStream(f); + write(file); + } catch (IOException e) { + System.err.println(e); + } + } + + /** + * Saves the BMP in a given file. + * + * @param url URL to write BMP + */ + public void write(URL url) { + try { + PCBinaryOutputStream file = new PCBinaryOutputStream(url); + write(file); + } catch (IOException e) { + System.err.println(e); + } + } + + /** + * Saves the BMP in a given file. + */ + void write(PCBinaryOutputStream file) { + try { + int rawData[] = new int[width*height]; + PixelGrabber grabber = new PixelGrabber(image, 0, 0, width, height, rawData, 0, width); + bmp_palette = new BmpPalette(grabber.getColorModel()); + + try { + grabber.grabPixels(); + } catch (InterruptedException e) { System.err.println(e); } + + ColorModel model = grabber.getColorModel(); + + bmp_fileheader.bfOffBits = bmp_fileheader.getSize() + + bmp_infoheader.getSize() + + bmp_palette.getSize(); + int x=0,y=0,i=0,j=0,k=0; + byte bitmap[]; + int w=width*3; + int h=height-1; + + // dword align width + if (w%4 != 0) + w += (4-(w%4)); + + if (model instanceof IndexColorModel) { + // not tested - (can't get IndexColorModel from PixelGrabber??!) + bitmap = new byte[width*bmp_infoheader.biHeight]; + for (y=h;y>=0;y--) { + i=(h-y)*width; + j=y*width; + for (x=0;x 0) { + file.skip(skip); + } + + scanlineSize = ((bmp_infoheader.biWidth*bmp_infoheader.biBitCount+31)/32)*4; + + if (bmp_infoheader.biSizeImage != 0) + bitplaneSize = bmp_infoheader.biSizeImage; + else + bitplaneSize = scanlineSize * bmp_infoheader.biHeight; + + rawData = new byte[bitplaneSize]; + file.readByteArray(rawData); + file.close(); + + } catch (IOException e) { + System.err.println(e); + } + + if (rawData != null) { + if (bmp_infoheader.biBitCount > 8) { + image = unpack24(rawData, scanlineSize); + } + else { + image = unpack08(rawData, scanlineSize); + } + } + rawData = null; + } + + Image unpack24(byte [] rawData, int scanlineSize) { + int b=0, k=0, x=0, y=0; + int [] data = new int[bmp_infoheader.biWidth * bmp_infoheader.biHeight]; + try { + for (y=0; y < bmp_infoheader.biHeight; y++) { + b=(bmp_infoheader.biHeight-1-y)*bmp_infoheader.biWidth; + k=y*scanlineSize; + for (x=0; x < bmp_infoheader.biWidth; x++) { + data[x+b] = 0xFF000000 | + (((int)(rawData[k++])) & 0xFF) | + (((int)(rawData[k++])) & 0xFF) << 8 | + (((int)(rawData[k++])) & 0xFF) << 16; + } + } + }catch (Exception e) {}; + return Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(bmp_infoheader.biWidth, bmp_infoheader.biHeight, ColorModel.getRGBdefault(), data, 0, bmp_infoheader.biWidth)); + } + + Image unpack08(byte [] rawData, int scanlineSize) { + int b=0, k=0, i=0, x=0, y=0; + byte [] data = new byte[bmp_infoheader.biWidth * bmp_infoheader.biHeight]; + try { + if (bmp_infoheader.biBitCount == 1) { + for (y=0; y < bmp_infoheader.biHeight; y++) { + b=(bmp_infoheader.biHeight-1-y)*bmp_infoheader.biWidth; + k=y*scanlineSize; + for (x=0; x < (bmp_infoheader.biWidth-8); x+=8) { + data[b+x+7] = (byte)((rawData[k]) & 1); + data[b+x+6] = (byte)((rawData[k] >>> 1) & 1); + data[b+x+5] = (byte)((rawData[k] >>> 2) & 1); + data[b+x+4] = (byte)((rawData[k] >>> 3) & 1); + data[b+x+3] = (byte)((rawData[k] >>> 4) & 1); + data[b+x+2] = (byte)((rawData[k] >>> 5) & 1); + data[b+x+1] = (byte)((rawData[k] >>> 6) & 1); + data[b+x] = (byte)((rawData[k] >>> 7) & 1); + k++; + } + for (i=7; i>=0 ; i--) { + if ((i+x)< bmp_infoheader.biWidth) { + data[b+x+i] = (byte)((rawData[k] >>> (7-i)) & 1); + } + } + } + } else if (bmp_infoheader.biBitCount == 4) { + for (y=0; y < bmp_infoheader.biHeight; y++) { + b=(bmp_infoheader.biHeight-1-y)*bmp_infoheader.biWidth; + k=y*scanlineSize; + for (x=0; x < (bmp_infoheader.biWidth-2); x+=2) { + data[b+x] = (byte)((rawData[k]>>4) & 0x0F); + data[b+x+1] = (byte)((rawData[k] & 0x0F)); + k+=1; + } + for (i=1; i>=0 ; i--) { + if ((i+x)< bmp_infoheader.biWidth) { + data[b+x+i] = (byte)((rawData[k] >>> ((1-i)<<2)) & 0x0F); + } + } + } + } else { + for (y=0; y < bmp_infoheader.biHeight; y++) { + b=(bmp_infoheader.biHeight-1-y)*bmp_infoheader.biWidth; + k=y*scanlineSize; + for (x=0; x < bmp_infoheader.biWidth; x++) { + data[x+b] = (byte)(rawData[k++] & 0xFF); + } + } + } + }catch (Exception e) {}; + + ColorModel colourModel = new IndexColorModel(bmp_infoheader.biBitCount, bmp_palette.length, bmp_palette.r, bmp_palette.g, bmp_palette.b); + return Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(bmp_infoheader.biWidth, bmp_infoheader.biHeight, colourModel, data, 0, bmp_infoheader.biWidth)); + } + + /** + * .BMP InfoHeader + */ + + class BmpInfoHeader { + int biSize = 40; /* InfoHeader Offset*/ + int biWidth; /* Width */ + int biHeight; /* Height */ + short biPlanes = 1; /* BitPlanes on Target Device */ + short biBitCount; /* Bits per Pixel */ + int biCompression; /* Bitmap Compression */ + int biSizeImage; /* Bitmap Image Size */ + int biXPelsPerMeter; /* Horiz Pixels Per Meter */ + int biYPelsPerMeter; /* Vert Pixels Per Meter */ + int biClrUsed; /* Number of ColorMap Entries */ + int biClrImportant; /* Number of important colours */ + + int getSize() { + return biSize; + } + + void read(PCBinaryInputStream file) { + try { + biSize = file.readInt(); + if (biSize == 12) { + biWidth = file.readShort(); + biHeight = file.readShort(); + biPlanes = file.readShort(); + biBitCount = file.readShort(); + } + else + { + biWidth = file.readInt(); + biHeight = file.readInt(); + biPlanes = file.readShort(); + biBitCount = file.readShort(); + biCompression = file.readInt(); + biSizeImage = file.readInt(); + biXPelsPerMeter = file.readInt(); + biYPelsPerMeter = file.readInt(); + biClrUsed = file.readInt(); + biClrImportant = file.readInt(); + } + } catch (IOException e) { System.err.println(e); } + + if (biSizeImage == 0) + biSizeImage = (((biWidth*biBitCount+31)>>5)<<2)*biHeight; + + if (biClrUsed == 0 && biBitCount < 16) + biClrUsed = 1 << biBitCount; + } + + void write(PCBinaryOutputStream file) { + try { + file.writeInt(biSize); + if (biSize == 12) { + file.writeShort((short)biWidth); + file.writeShort((short)biHeight); + file.writeShort(biPlanes); + file.writeShort(biBitCount); + } + else { + file.writeInt(biWidth); + file.writeInt(biHeight); + file.writeShort(biPlanes); + file.writeShort(biBitCount); + file.writeInt(biCompression); + file.writeInt(biSizeImage); + file.writeInt(biXPelsPerMeter); + file.writeInt(biYPelsPerMeter); + file.writeInt(biClrUsed); + file.writeInt(biClrImportant); + } + } catch (IOException e) { + System.err.println(e); + } + } + } + + /** + * .BMP FileHeader + */ + + class BmpFileheader { + byte bfType[] = {'B','M'}; /* Type */ + int bfSize; /* File Size */ + short bfReserved1=0; /* Reserved 1 */ + short bfReserved2=0; /* Reserved 2 */ + int bfOffBits; /* Offset to Data */ + + int getSize() { + return 14; + } + + void read(PCBinaryInputStream file) { + try { + bfType[0] = file.readByte(); + bfType[1] = file.readByte(); + if (bfType[0] != 'B' && bfType[1] != 'M') + throw new IOException("Invalid BMP 3.0 File."); + bfSize = file.readInt(); + bfReserved1 = file.readShort(); + bfReserved2 = file.readShort(); + bfOffBits = file.readInt(); + } catch (IOException e) { + System.err.println(e); + } + } + + void write(PCBinaryOutputStream file) { + try { + file.writeByte(bfType[0]); + file.writeByte(bfType[1]); + file.writeInt(bfSize); + file.writeShort(bfReserved1); + file.writeShort(bfReserved2); + file.writeInt(bfOffBits); + } catch (IOException e) { + System.err.println(e); + } + } + } + + /** + * .BMP Palette + */ + + class BmpPalette { + int length; + byte r[]; + byte g[]; + byte b[]; + + public BmpPalette(int length) + { + this.length = length; + r = new byte[length]; + g = new byte[length]; + b = new byte[length]; + } + + int getSize() { + return length*4; + } + + public BmpPalette(ColorModel colourModel) { + if (colourModel instanceof IndexColorModel) { + IndexColorModel indexColourModel = (IndexColorModel)colourModel; + this.length = indexColourModel.getMapSize(); + r = new byte[length]; + indexColourModel.getReds(r); + g = new byte[length]; + indexColourModel.getGreens(g); + b = new byte[length]; + indexColourModel.getBlues(b); + bmp_infoheader.biBitCount = (short)indexColourModel.getPixelSize(); + bmp_infoheader.biClrUsed = length; + } + } + + void read(PCBinaryInputStream file) { + if (length > 0) { + try { + byte reserved; + for (int i=0; i < length; i++) { + b[i] = file.readByte(); // blue + g[i] = file.readByte(); // green + r[i] = file.readByte(); // red + reserved = file.readByte(); // reserved + } + } catch (IOException e) { + System.err.println(e); + } + } + } + + void write(PCBinaryOutputStream file) { + if (length > 0) { + try { + byte reserved = 0; + for (int i=0; i < length; i++) { + file.writeByte(b[i]); + file.writeByte(g[i]); + file.writeByte(r[i]); + file.writeByte(reserved); + } + } catch (IOException e) { + System.err.println(e); + } + } + } + } + +} \ No newline at end of file diff --git a/src/tm/osbaldeston/io/PCBinaryInputStream.java b/src/tm/osbaldeston/io/PCBinaryInputStream.java new file mode 100644 index 0000000..ca1193f --- /dev/null +++ b/src/tm/osbaldeston/io/PCBinaryInputStream.java @@ -0,0 +1,69 @@ +package tm.osbaldeston.io; + +/** + * PCBinaryInputStream + * + * @author Richard J.Osbaldeston + * @version 1.1 02/08/98 + * @copyright Richard J.Osbaldeston (http://www.osbald.co.uk) + */ + +import java.io.*; +import java.net.URL; + +public class PCBinaryInputStream { + + DataInputStream file; + + public PCBinaryInputStream(File f) throws IOException + { + file = new DataInputStream(new BufferedInputStream(new FileInputStream(f))); + } + + public PCBinaryInputStream(URL url) throws IOException + { + file = new DataInputStream(new BufferedInputStream((url.openConnection()).getInputStream())); + } + + public int readInt() throws IOException + { + int i=file.readInt(); + return ((i<<24)|((i&0x0000FF00)<<8)|((i&0x00FF0000)>>>8)|(i>>>24)); + } + + public short readShort() throws IOException + { + int i=file.readUnsignedShort(); + return (short)((i<<8)|(i>>>8)); + } + + public byte readByte() throws IOException + { + return (byte)file.readUnsignedByte(); + } + + public void readByteArray(byte b[]) throws IOException + { + file.readFully(b); + } + + public void skip(long nbytes) throws IOException + { + file.skip(nbytes); + } + + public void close() throws IOException + { + file.close(); + file=null; + } + + public void finalize() { + if (file != null) { + try { + close(); + } catch (IOException e) {} + } + } +} + diff --git a/src/tm/osbaldeston/io/PCBinaryOutputStream.java b/src/tm/osbaldeston/io/PCBinaryOutputStream.java new file mode 100644 index 0000000..a2d0b81 --- /dev/null +++ b/src/tm/osbaldeston/io/PCBinaryOutputStream.java @@ -0,0 +1,75 @@ +package tm.osbaldeston.io; + +/** + * PCBinaryOutputStream + * + * @author Richard J.Osbaldeston + * @version 1.1 02/08/98 + * @copyright Richard J.Osbaldeston (http://www.osbald.co.uk) + */ + +import java.io.*; +import java.net.URL; + +public class PCBinaryOutputStream { + + DataOutputStream file; + + public PCBinaryOutputStream(File f) throws IOException + { + file = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f))); + } + + public PCBinaryOutputStream(URL url) throws IOException + { + file = new DataOutputStream(new BufferedOutputStream((url.openConnection()).getOutputStream())); + } + + public void writeInt(int i) throws IOException + { + int j=(i<<24)|((i&0x0000FF00)<<8)|((i&0x00FF0000)>>>8)|(i>>>24); + file.writeInt(j); + } + + public void writeShort(short i) throws IOException + { + int j=(i<<8)|(i>>>8); + file.writeShort((short)j); + } + + public void writeByte(byte i) throws IOException + { + file.writeByte(i); + } + + public void writeByteArray(byte b[]) throws IOException + { + file.write(b); + } + + public long getFilePointer() throws IOException + { + return file.size(); + } + + public long size() throws IOException + { + return file.size(); + } + + public void close() throws IOException + { + file.flush(); + file.close(); + file=null; + } + + public void finalize() { + if (file != null) { + try { + close(); + } catch (IOException e) {} + } + } +} + diff --git a/src/tm/reversibleaction/ReversibleAction.java b/src/tm/reversibleaction/ReversibleAction.java new file mode 100644 index 0000000..7871ee2 --- /dev/null +++ b/src/tm/reversibleaction/ReversibleAction.java @@ -0,0 +1,80 @@ +/* +* +* 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.reversibleaction; + +import java.util.Date; + +/** +* +* Abstract class for handling reversible actions (undoable/redoable). +* Every type of action needs to subclass this class and implement the +* below methods. +* +**/ + +public abstract class ReversibleAction { + + private String presentationName; + private Date timeStamp; + +/** +* +* Creates a reversible action with the given presentation name. +* +**/ + + public ReversibleAction(String presentationName) { + this.presentationName = presentationName; + timeStamp = new Date(); + } + +/** +* +* Subclasses must implement these. +* +**/ + + public abstract void undo(); + public abstract void redo(); + public abstract boolean canUndo(); + public abstract boolean canRedo(); + +/** +* +* Gets the presentation name. +* The presentation name is the text that is displayed after "Undo"/"Redo" +* when this action can be undone/redone. +* +**/ + + public String getPresentationName() { + return presentationName; + } + +/** +* +* Gets the timestamp for this action, i.e. when it was first executed. +* +**/ + + public Date getTimeStamp() { + return timeStamp; + } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversibleActionsSelectionAction.java b/src/tm/reversibleaction/ReversibleActionsSelectionAction.java new file mode 100644 index 0000000..8454668 --- /dev/null +++ b/src/tm/reversibleaction/ReversibleActionsSelectionAction.java @@ -0,0 +1,137 @@ +/* +* +* 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.reversibleaction; + +import tm.treenodes.BookmarkItemNode; +import tm.canvases.TMTileCanvas; +import tm.canvases.TMEditorCanvas; + + +/** +* +* Allows undo/redo of an action that modifies the entire image/selection, +* such as flip and rotate. +* +**/ + +public class ReversibleActionsSelectionAction extends ReversibleAction { + + public static final int FLIP_ACTION = 1; + public static final int MIRROR_ACTION = 2; + public static final int ROTATELEFT_ACTION = 3; + public static final int ROTATERIGHT_ACTION = 4; + public static final int SHIFTLEFT_ACTION = 5; + public static final int SHIFTRIGHT_ACTION = 6; + public static final int SHIFTUP_ACTION = 7; + public static final int SHIFTDOWN_ACTION = 8; + + private TMTileCanvas canvas; + private int action; + private BookmarkItemNode bookmark; + + public ReversibleActionsSelectionAction(String presentationName, TMTileCanvas canvas, int action) { + super(presentationName); + this.canvas = canvas; + this.action = action; + if (canvas instanceof TMEditorCanvas) { + bookmark = ((TMEditorCanvas)canvas).getView().createBookmark(""); + } + } + + // undo by performing the "inverse" action. + public void undo() { + if (canvas instanceof TMEditorCanvas) { + ((TMEditorCanvas)canvas).getView().gotoBookmark(bookmark); + } + if (action == FLIP_ACTION) { + canvas.flip(); + } + else if (action == MIRROR_ACTION) { + canvas.mirror(); + } + else if (action == ROTATELEFT_ACTION) { + canvas.rotateRight(); + if (canvas instanceof TMEditorCanvas) { + bookmark = ((TMEditorCanvas)canvas).getView().createBookmark(""); + } + } + else if (action == ROTATERIGHT_ACTION) { + canvas.rotateLeft(); + if (canvas instanceof TMEditorCanvas) { + bookmark = ((TMEditorCanvas)canvas).getView().createBookmark(""); + } + } + else if (action == SHIFTLEFT_ACTION) { + canvas.shiftRight(); + } + else if (action == SHIFTRIGHT_ACTION) { + canvas.shiftLeft(); + } + else if (action == SHIFTUP_ACTION) { + canvas.shiftDown(); + } + else if (action == SHIFTDOWN_ACTION) { + canvas.shiftUp(); + } + canvas.redraw(); + } + + // redo by simply performing the original action again. + public void redo() { + if (canvas instanceof TMEditorCanvas) { + ((TMEditorCanvas)canvas).getView().gotoBookmark(bookmark); + } + if (action == FLIP_ACTION) { + canvas.flip(); + } + else if (action == MIRROR_ACTION) { + canvas.mirror(); + } + else if (action == ROTATELEFT_ACTION) { + canvas.rotateLeft(); + if (canvas instanceof TMEditorCanvas) { + bookmark = ((TMEditorCanvas)canvas).getView().createBookmark(""); + } + } + else if (action == ROTATERIGHT_ACTION) { + canvas.rotateRight(); + if (canvas instanceof TMEditorCanvas) { + bookmark = ((TMEditorCanvas)canvas).getView().createBookmark(""); + } + } + else if (action == SHIFTLEFT_ACTION) { + canvas.shiftLeft(); + } + else if (action == SHIFTRIGHT_ACTION) { + canvas.shiftRight(); + } + else if (action == SHIFTUP_ACTION) { + canvas.shiftUp(); + } + else if (action == SHIFTDOWN_ACTION) { + canvas.shiftDown(); + } + canvas.redraw(); + } + + public boolean canUndo() { return true; } + + public boolean canRedo() { return true; } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversibleAddBookmarkAction.java b/src/tm/reversibleaction/ReversibleAddBookmarkAction.java new file mode 100644 index 0000000..379f54f --- /dev/null +++ b/src/tm/reversibleaction/ReversibleAddBookmarkAction.java @@ -0,0 +1,53 @@ +/* +* +* 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.reversibleaction; + +import tm.treenodes.*; + +/** +* +* Allows undo/redo of a bookmark addition. +* +**/ + +public class ReversibleAddBookmarkAction extends ReversibleAction { + + private BookmarkItemNode bookmark; + private FolderNode folder; + + public ReversibleAddBookmarkAction(BookmarkItemNode bookmark) { + super("Add Bookmark"); // i18n + this.bookmark = bookmark; + this.folder = (FolderNode)bookmark.getParent(); + } + + public void undo() { + folder.remove(bookmark); + // ui.buildBookmarksMenu(); + } + + public void redo() { + folder.add(bookmark); + // ui.buildBookmarksMenu(); + } + + public boolean canUndo() { return true; } + public boolean canRedo() { return true; } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversibleApplySelectionAction.java b/src/tm/reversibleaction/ReversibleApplySelectionAction.java new file mode 100644 index 0000000..ee28024 --- /dev/null +++ b/src/tm/reversibleaction/ReversibleApplySelectionAction.java @@ -0,0 +1,47 @@ +package tm.reversibleaction; + +import tm.canvases.TMEditorCanvas; +import tm.canvases.TMSelectionCanvas; + +/** + * + * Allows the deletion of a selection + * + **/ + +public class ReversibleApplySelectionAction extends ReversibleAction { + private TMSelectionCanvas selection; + private TMEditorCanvas owner; + private ReversibleTileModifyAction modifiedTiles; + + public ReversibleApplySelectionAction(TMSelectionCanvas selection, TMEditorCanvas owner) + { + super("Apply_Selection"); + this.selection = selection; + this.owner = owner; + this.modifiedTiles = owner.encodeSelection(false); + } + + public void undo() + { + int x = selection.getX() / selection.getScaledTileDim(); + int y = selection.getY() / selection.getScaledTileDim(); + modifiedTiles.undo(); + owner.showSelection(selection, x, y); + } + + public void redo() + { + owner.encodeSelection(false); + } + + public boolean canUndo() + { + return true; + } + + public boolean canRedo() + { + return true; + } +} diff --git a/src/tm/reversibleaction/ReversibleClearAction.java b/src/tm/reversibleaction/ReversibleClearAction.java new file mode 100644 index 0000000..6bc8d9f --- /dev/null +++ b/src/tm/reversibleaction/ReversibleClearAction.java @@ -0,0 +1,56 @@ +/* +* +* 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.reversibleaction; + +import tm.canvases.TMSelectionCanvas; +import tm.canvases.TMEditorCanvas; + +/** +* +* Allows undo/redo of the Clear operation. +* +**/ + +public class ReversibleClearAction extends ReversibleAction { + + private TMSelectionCanvas selection; + private TMEditorCanvas owner; + private int x, y; + + public ReversibleClearAction(TMSelectionCanvas selection, TMEditorCanvas owner) { + super("Clear Selection"); // i18n + this.selection = selection; + this.owner = owner; + x = selection.getX() / selection.getScaledTileDim(); + y = selection.getY() / selection.getScaledTileDim(); + } + + public void undo() { + owner.showSelection(selection, x, y); + } + + public void redo() { + owner.remove(selection); + owner.repaint(); + } + + public boolean canUndo() { return true; } + public boolean canRedo() { return true; } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversibleCutAction.java b/src/tm/reversibleaction/ReversibleCutAction.java new file mode 100644 index 0000000..060b080 --- /dev/null +++ b/src/tm/reversibleaction/ReversibleCutAction.java @@ -0,0 +1,56 @@ +/* +* +* 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.reversibleaction; + +import tm.canvases.TMSelectionCanvas; +import tm.canvases.TMEditorCanvas; + +/** +* +* Allows undo/redo for the Cut operation. +* +**/ + +public class ReversibleCutAction extends ReversibleAction { + + private TMSelectionCanvas selection; + private TMEditorCanvas owner; + private int x, y; + + public ReversibleCutAction(TMSelectionCanvas selection, TMEditorCanvas owner) { + super("Cut"); // i18n + this.selection = selection; + this.owner = owner; + x = selection.getX() / selection.getScaledTileDim(); + y = selection.getY() / selection.getScaledTileDim(); + } + + public void undo() { + owner.showSelection(selection, x, y); + } + + public void redo() { + owner.remove(selection); + owner.repaint(); + } + + public boolean canUndo() { return true; } + public boolean canRedo() { return true; } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversibleMoveSelectionAction.java b/src/tm/reversibleaction/ReversibleMoveSelectionAction.java new file mode 100644 index 0000000..f6c9c77 --- /dev/null +++ b/src/tm/reversibleaction/ReversibleMoveSelectionAction.java @@ -0,0 +1,58 @@ +/* +* +* 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.reversibleaction; + +import tm.canvases.TMSelectionCanvas; + +/** +* +* Allows undo/redo of moving a selection. +* +**/ + +public class ReversibleMoveSelectionAction extends ReversibleAction { + + private TMSelectionCanvas selection; + private int oldX, oldY; + private int newX, newY; + + public ReversibleMoveSelectionAction(TMSelectionCanvas selection, int oldX, int oldY, int newX, int newY) { + super("Move Selection"); // i18n + this.selection = selection; + this.oldX = oldX; + this.oldY = oldY; + this.newX = newX; + this.newY = newY; + } + + public void undo() { + int dim = selection.getScaledTileDim(); + selection.setLocation(oldX * dim, oldY * dim); + } + + public void redo() { + int dim = selection.getScaledTileDim(); + selection.setLocation(newX * dim, newY * dim); + + } + + public boolean canUndo() { return true; } + public boolean canRedo() { return true; } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversibleNewSelectionAction.java b/src/tm/reversibleaction/ReversibleNewSelectionAction.java new file mode 100644 index 0000000..c89d117 --- /dev/null +++ b/src/tm/reversibleaction/ReversibleNewSelectionAction.java @@ -0,0 +1,56 @@ +/* +* +* 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.reversibleaction; + +import tm.canvases.TMEditorCanvas; +import tm.canvases.TMSelectionCanvas; + +/** +* +* Allows undo/redo of making a new selection. +* +**/ + +public class ReversibleNewSelectionAction extends ReversibleAction { + private TMSelectionCanvas newSelection; + private TMEditorCanvas owner; + private int newX, newY; + + public ReversibleNewSelectionAction(TMSelectionCanvas newSelection, TMEditorCanvas owner) { + super("New_Selection"); // i18n + this.newSelection = newSelection; + this.owner = owner; + newX = newSelection.getX() / newSelection.getScaledTileDim(); + newY = newSelection.getY() / newSelection.getScaledTileDim(); + } + + public void undo() { + owner.encodeSelection(false); // newSelection + owner.repaint(); + } + + public void redo() { + owner.makeSelection(newSelection); + owner.repaint(); + } + + public boolean canUndo() { return true; } + public boolean canRedo() { return true; } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversiblePaletteEditAction.java b/src/tm/reversibleaction/ReversiblePaletteEditAction.java new file mode 100644 index 0000000..c712bc4 --- /dev/null +++ b/src/tm/reversibleaction/ReversiblePaletteEditAction.java @@ -0,0 +1,59 @@ +/* +* +* 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.reversibleaction; + +import tm.TMPalette; +import tm.ui.TMView; + +/** +* +* Allows undo/redo of a palette entry modification. +* +**/ + +public class ReversiblePaletteEditAction extends ReversibleAction { + + private int colorIndex; + private int oldColor; + private int newColor; + private TMPalette palette; + + public ReversiblePaletteEditAction(TMView view, TMPalette palette, int colorIndex, int oldColor, int newColor) { + super("Edit Palette"); // i18n + this.palette = palette; + this.colorIndex = colorIndex; + this.oldColor = oldColor; + this.newColor = newColor; + } + + public void undo() { + palette.setEntryRGB(colorIndex, oldColor); + // paletteChanged() + } + + public void redo() { + palette.setEntryRGB(colorIndex, newColor); + // paletteChanged() + } + + public boolean canUndo() { return true; } + + public boolean canRedo() { return true; } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversiblePasteSelectionAction.java b/src/tm/reversibleaction/ReversiblePasteSelectionAction.java new file mode 100644 index 0000000..31d6341 --- /dev/null +++ b/src/tm/reversibleaction/ReversiblePasteSelectionAction.java @@ -0,0 +1,42 @@ +package tm.reversibleaction; + +import tm.canvases.TMEditorCanvas; +import tm.canvases.TMSelectionCanvas; + +/** + * Manages the Paste undo/redo action + */ +public class ReversiblePasteSelectionAction extends ReversibleAction{ + private TMSelectionCanvas pastedSel; + private TMEditorCanvas owner; + + public ReversiblePasteSelectionAction(TMSelectionCanvas pastedSel, TMEditorCanvas owner) + { + super("Paste"); + this.pastedSel = pastedSel; + this.owner = owner; + + owner.showSelection(pastedSel, 0, 0); + } + + public boolean canUndo() + { + return true; + } + + public boolean canRedo() + { + return true; + } + + public void undo() + { + owner.remove(pastedSel); + owner.redraw(); + } + + public void redo() + { + owner.showSelection(pastedSel,0,0); + } +} diff --git a/src/tm/reversibleaction/ReversibleStretchAction.java b/src/tm/reversibleaction/ReversibleStretchAction.java new file mode 100644 index 0000000..6e93aa0 --- /dev/null +++ b/src/tm/reversibleaction/ReversibleStretchAction.java @@ -0,0 +1,63 @@ +/* +* +* 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.reversibleaction; + +import tm.canvases.TMSelectionCanvas; + +/** +* +* Allows undo/redo of selection stretching. +* +**/ + +public class ReversibleStretchAction extends ReversibleAction { + + private TMSelectionCanvas canvas; + private int oldCols; + private int oldRows; + private int newCols; + private int newRows; + private byte[] oldBits; + + public ReversibleStretchAction(TMSelectionCanvas canvas, int newCols, int newRows) { + super("Stretch Selection"); // i18n + this.canvas = canvas; + oldCols = canvas.getCols(); + oldRows = canvas.getRows(); + oldBits = canvas.getBits(); + this.newCols = newCols; + this.newRows = newRows; + } + + public void undo() { + canvas.setGridSize(oldCols, oldRows); + canvas.setBits(oldBits); + canvas.unpackPixels(); + canvas.redraw(); + } + + public void redo() { + canvas.stretchTo(newCols, newRows); + canvas.redraw(); + } + + public boolean canUndo() { return true; } + public boolean canRedo() { return true; } + +} \ No newline at end of file diff --git a/src/tm/reversibleaction/ReversibleTileModifyAction.java b/src/tm/reversibleaction/ReversibleTileModifyAction.java new file mode 100644 index 0000000..153d4fe --- /dev/null +++ b/src/tm/reversibleaction/ReversibleTileModifyAction.java @@ -0,0 +1,86 @@ +/* +* +* 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.reversibleaction; + +import java.awt.Point; +import tm.treenodes.BookmarkItemNode; +import tm.canvases.TMEditorCanvas; + +/** +* +* Allows undo/redo of drawing operations. +* Records the view settings when the edit was done, +* the grid coordinates of the tiles that were modified, +* the old pixels, and the new pixels. +* +**/ + +public class ReversibleTileModifyAction extends ReversibleAction { + + TMEditorCanvas canvas; + BookmarkItemNode bookmark; + Point[] gridCoords; + int[] oldPixels; + int[] newPixels; + + public ReversibleTileModifyAction( + String presentationName, + TMEditorCanvas canvas, + BookmarkItemNode bookmark, + Point[] gridCoords, + int[] oldPixels, + int[] newPixels) { + + super(presentationName); + this.canvas = canvas; + this.bookmark = bookmark; + this.gridCoords = gridCoords; + this.oldPixels = oldPixels; + this.newPixels = newPixels; + } + + public void undo() { + canvas.getView().gotoBookmark(bookmark); + for (int i=0; i 0) { + if (bytesLeft > CHUNK_SIZE) { + try { + bis.read(contents, contents.length - bytesLeft, CHUNK_SIZE); + } + catch (Exception e) { } + bytesLeft -= CHUNK_SIZE; + } + else { + try { + bis.read(contents, contents.length - bytesLeft, bytesLeft); + } + catch (Exception e) { } + bytesLeft = 0; + } + yield(); + } + try { + bis.close(); + } catch (Exception e) { } + // done loading data + } + + public byte[] getContents() { + return contents; + } + + public void killContentsRef() { + contents = null; + } + +} \ No newline at end of file diff --git a/src/tm/threads/FileSaverThread.java b/src/tm/threads/FileSaverThread.java new file mode 100644 index 0000000..1e8f3ef --- /dev/null +++ b/src/tm/threads/FileSaverThread.java @@ -0,0 +1,83 @@ +/* +* +* 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.threads; + +import java.io.*; + +/** +* +* Thread for writing a buffer to a file. +* +**/ + +public class FileSaverThread extends ProgressThread { + + private static final int CHUNK_SIZE = 16384; + private RandomAccessFile raf=null; + private int bytesLeft; + private byte[] contents; + + public FileSaverThread(byte[] contents, File file) + throws FileNotFoundException, IOException { + super(); + this.contents = contents; + try { + raf = new RandomAccessFile(file, "rw"); + raf.seek(0); + } + catch (FileNotFoundException e) { + throw e; + } + catch (IOException e) { + throw e; + } + bytesLeft = contents.length; + this.setPriority(NORM_PRIORITY); + } + + public int getPercentageCompleted() { + int result = (int)(((long)contents.length - (long)bytesLeft) * 100 / (long)contents.length); + return result; + } + + public void run() { + while (bytesLeft > 0) { + if (bytesLeft > CHUNK_SIZE) { + try { + raf.write(contents, contents.length - bytesLeft, CHUNK_SIZE); + } + catch (Exception e) { } + bytesLeft -= CHUNK_SIZE; + } + else { + try { + raf.write(contents, contents.length - bytesLeft, bytesLeft); + } + catch (Exception e) { } + bytesLeft = 0; + } + yield(); + } + try { + raf.close(); + } catch (Exception e) { } + // done saving data + } + +} \ No newline at end of file diff --git a/src/tm/threads/ProgressThread.java b/src/tm/threads/ProgressThread.java new file mode 100644 index 0000000..f61af4e --- /dev/null +++ b/src/tm/threads/ProgressThread.java @@ -0,0 +1,23 @@ +/* +* +* 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.threads; + +public abstract class ProgressThread extends Thread { + public abstract int getPercentageCompleted(); +} diff --git a/src/tm/tilecodecs/CompositeTileCodec.java b/src/tm/tilecodecs/CompositeTileCodec.java new file mode 100644 index 0000000..88d297f --- /dev/null +++ b/src/tm/tilecodecs/CompositeTileCodec.java @@ -0,0 +1,102 @@ +/* +* +* 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.tilecodecs; + +/** +* +* Composite tile codec. +* A composite tile is a tile that is built up of several +* standard tiles. As an example, consider a 3bpp tile that consists +* of a single 2bpp non-interleaved tile followed by a single 1bpp tile. +* Such a format cannot be accommodated by the standard planar tile codec +* (PlanarTileCodec). However, it can be accommodated by instantiating several +* PlanarTileCodecs, decoding planar tiles separately and then "overlaying" them +* on top of each other. This class provides just this kind of functionality. +* It allows more flexibility in the tile formats, but is probably a bit +* slower. +* +**/ + +public class CompositeTileCodec extends TileCodec { + + private TileCodec[] codecs; + +/** +* +* Creates a composite tile codec. +* codecs is an array of codecs that will be used to build +* a tile, going from low to high bitplanes. +* +**/ + + public CompositeTileCodec(String id, int bpp, TileCodec[] codecs, String description) { + super(id, bpp, description); + this.codecs = codecs; + } + +/** +* +* Decodes a tile. +* +* @param bits An array of ints holding encoded tile data in each LSB +* @param ofs Where to start decoding from in the array +* +**/ + + public int[] decode(byte[] bits, int ofs, int stride) { + // decode the first sub-tile + int[] tilePixels = codecs[0].decode(bits, ofs, stride); + System.arraycopy(tilePixels, 0, pixels, 0, tilePixels.length); + // decode remaining sub-tiles + int p = codecs[0].getBitsPerPixel(); + for (int i=1; i>= p; + } + codecs[i].encode(pixels, bits, ofs, stride); + p += codecs[i].getBitsPerPixel(); + } + } + +} \ No newline at end of file diff --git a/src/tm/tilecodecs/DirectColorTileCodec.java b/src/tm/tilecodecs/DirectColorTileCodec.java new file mode 100644 index 0000000..5b3cde4 --- /dev/null +++ b/src/tm/tilecodecs/DirectColorTileCodec.java @@ -0,0 +1,257 @@ +/* +* +* 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.tilecodecs; + +/** +* +* 16, 24, and 32-bit direct-color (ARGB) tile codec. +* # of bits per color component must not exceed 8. +* The color component masks must be directly adjacent. +* +**/ + +public class DirectColorTileCodec extends TileCodec { + + public static final int LITTLE_ENDIAN=1; + public static final int BIG_ENDIAN=2; + + private static int endianness; // LITTLE_ENDIAN | BIG_ENDIAN + + 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; + + // number of bytes that hold one compressed pixel. + private int bytesPerPixel; + + private int startShift; + private int shiftStep; + +/** +* +* Creates a direct-color tile codec. +* +**/ + + public DirectColorTileCodec(String id, int bpp, int rmask, int gmask, int bmask, int amask, String description) { + super(id, getByteIntegralBitCount(bpp), description); + bytesPerPixel = bitsPerPixel / 8; // 2, 3 or 4 + 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); + + setEndianness(LITTLE_ENDIAN); // default + } + +/** +* Sets the endianness. +**/ + + 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 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 + } + +/** +* +* Decodes a tile. +* +**/ + + public int[] decode(byte[] bits, int ofs, int stride) { + int v, r, g, b, a, s; + int pos=0; + stride *= bytesPerRow; + for (int i=0; i<8; i++) { + // do one row of pixels + for (int j=0; j<8; j++) { + + // get encoded pixel + s = startShift; + v = 0; + for (int k=0; k>= -rshift; + } + else { + r <<= rshift; + } + + // decode G component + g = v & gmask; + if (gshift < 0) { + g >>= -gshift; + } + else { + g <<= gshift; + } + + // decode B component + b = v & bmask; + if (bshift < 0) { + b >>= -bshift; + } + else { + b <<= bshift; + } + + // decode A component + a = v & amask; + if (ashift < 0) { + a >>= -ashift; + } + else { + a <<= ashift; + } + + // final pixel + pixels[pos++] = a | r | g | b; + } + ofs += stride; + } + return pixels; + } + +/** +* +* Encodes a tile. +* +**/ + + public void encode(int[] pixels, byte[] bits, int ofs, int stride) { + int v, r, g, b, a, s, argb; + int pos=0; + stride *= bytesPerRow; + for (int i=0; i<8; i++) { + // do one row of pixels + for (int j=0; j<8; j++) { + + // get decoded pixel + argb = pixels[pos++]; + + // 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 + s = startShift; + v = a | r | g | b; + for (int k=0; k> s); + s += shiftStep; + } + + } + ofs += stride; + } + } + +/** +* +* Gets the least number of whole bytes that are required to store +* bits bits of information. +* +**/ + + private static int getByteIntegralBitCount(int bits) { + int bytes = bits / 8; + int extrabits = bits % 8; + if (extrabits != 0) bytes++; + return bytes * 8; + } + +} \ No newline at end of file diff --git a/src/tm/tilecodecs/LinearTileCodec.java b/src/tm/tilecodecs/LinearTileCodec.java new file mode 100644 index 0000000..e14241a --- /dev/null +++ b/src/tm/tilecodecs/LinearTileCodec.java @@ -0,0 +1,166 @@ +/* +* +* 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.tilecodecs; + +/** +* +* Linear palette-indexed 8x8 tile codec. Max. 8 bits per pixel. +* bitsPerPixel mod 2 = 0 (so only 1, 2, 4, 8bpp possible). +* +* Some notes on "pixel ordering": +* +* I define pixel order for a row like so: 0 1 2 3 4 5 6 7. +* In other words, pixels are numbered from leftmost to rightmost, +* as they appear on screen. +* I define bit order in a byte like so: 7 6 5 4 3 2 1 0. +* In other words, bits are numbered from rightmost (high) to leftmost (low). +* +* For formats with less than 8 bits per pixel, it is then an +* issue how bits are extracted from the bytes of encoded tile data +* to form the order of pixels stated above. +* +* Note that this applies at the PIXEL-level, not bit-, byte- or row-level; +* this is why ordering is not an issue for 8bpp tiles, since each byte +* contains only one pixel. It is assumed that the bytes themselves are +* always stored in-order (0, 1, 2, 3, ...). Similary, it is assumed that +* the individual bits of a pixel are always stored from high -> low. +* +* The familiar terms "little endian" and "big endian" are not appropriate to +* use here, as I consider them to apply at the byte-level. To avoid any +* confusion or ambiguity, I therefore use the expressions "in-order" and +* "reverse-order" to refer to the ordering of pixels within a byte. +* This is still a bit tricky since the order of pixels and bits above are +* reverses of eachother to begin with, but shouldn't be too confusing: +* +* I define "in-order" to mean that the order of pixel data as stored in a byte +* complies with the order of pixels to be rendered, from left to right. For +* 1bpp, this is simple: in-order means that bit 7 corresponds to pixel 0, +* bit 6 to pixel 1 and so on. Reverse-order means that bit 7 corresponds to +* pixel 7, bit 6 corresponds to pixel 6 and so on (in other words, the bits +* have to be reversed from left to right in order for the pixel order to be +* correct). Diagrammatically: +* +* Pixels: 01234567 +* |||||||| (In-order) +* Bits: 76543210 +* +* Pixels: 01234567 +* |||||||| (Reverse-order) +* Bits: 01234567 +* +* This extends quite intuitively to the cases of 2 and 4 bits per pixel. +* I will give one example of each. +* +* 2bpp, in-order: +* +* Pixels: 0 1 2 3 +* | | | | +* Bits: 76 54 32 10 +* +* 4bpp, reverse-order: +* +* Pixels: 0 1 +* | | +* Bits: 3210 7654 +* +**/ + +public class LinearTileCodec extends TileCodec { + + public static final int IN_ORDER=1; + public static final int REVERSE_ORDER=2; + + protected int ordering; + protected int pixelsPerByte; + protected int pixelMask; + protected int startPixel; + protected int boundary; + protected int step; + +/** +* Constructor. +**/ + + public LinearTileCodec(String id, int bitsPerPixel, int ordering, String description) { + super(id, bitsPerPixel, description); // <= 8 + this.ordering = ordering; // IN_ORDER or REVERSE_ORDER + pixelsPerByte = 8 / bitsPerPixel; + pixelMask = (int)(colorCount - 1); + + if (IN_ORDER == ordering) { + startPixel = pixelsPerByte-1; + boundary = -1; + step = -1; + } + else { // REVERSE_ORDER + startPixel = 0; + boundary = pixelsPerByte; + step = 1; + } + } + +/** +* +* Decodes a tile. +* +**/ + + public int[] decode(byte[] bits, int ofs, int stride) { + int pos=0; + stride *= bytesPerRow; + for (int i=0; i<8; i++) { + // do one row + for (int k=0; k> bitsPerPixel*m) & pixelMask; + } + } + ofs += stride; + } + return pixels; + } + +/** +* +* Encodes a tile. +* +**/ + + public void encode(int[] pixels, byte[] bits, int ofs, int stride) { + int pos = 0; + stride *= bytesPerRow; + for (int i=0; i<8; i++) { + // do one row + for (int k=0; k=0; k--) { + // do one pixel + bitsToPixelsLookup[i][j][7-k] = ((j >> k) & 1) << i; + } + } + } + } + } + +/** +* +* Decodes a tile. +* +* @param bits An array of ints holding encoded tile data in each LSB +* @param ofs Where to start decoding from in the array +* +**/ + + public int[] decode(byte[] bits, int ofs, int stride) { + int pos=0; + stride++; + stride *= bytesPerRow; + for (int i=0; i<8; i++) { + // do one row of pixels + for (int j=0; j> k) & 0x01) << (7-j); + } + pos++; + } + ofs += stride; + } + } + +} \ No newline at end of file diff --git a/src/tm/tilecodecs/TileCodec.java b/src/tm/tilecodecs/TileCodec.java new file mode 100644 index 0000000..0df3931 --- /dev/null +++ b/src/tm/tilecodecs/TileCodec.java @@ -0,0 +1,150 @@ +/* +* +* 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.tilecodecs; + +/** +* +* Abstract class for 8x8 ("atomic") tile codecs. +* To add a new tile format, simply extend this class and implement decode() and encode(). +* +**/ + +public abstract class TileCodec { + + public static final int MODE_1D=1; + public static final int MODE_2D=2; + + private String id; + private String description; + protected int[] pixels; // destination for DEcoded tile data + protected int bitsPerPixel; + protected int bytesPerRow; // row = 8 pixels + protected long colorCount; + protected int tileSize; // size of one encoded tile + +/** +* +* Constructor. Every subclass must call this with argument bitsPerPixel. +* +* @param bitsPerPixel Duh! +* +**/ + + public TileCodec(String id, int bitsPerPixel, String description) { + this.id = id; + this.bitsPerPixel = bitsPerPixel; + this.description = description; + bytesPerRow = bitsPerPixel; // because (bitsPerPixel*8)/8 = bitsPerPixel + tileSize = bytesPerRow*8; + colorCount = 1 << bitsPerPixel; + pixels = new int[8*8]; + } + +/** +* +* Decodes a tile. +* +* @param bits An array of encoded tile data +* @param ofs Start offset of tile in bits array +* +**/ + + public abstract int[] decode(byte[] bits, int ofs, int stride); + +/** +* +* Encodes a tile. +* +* @param pixels An array of decoded tile data +* +**/ + + public abstract void encode(int[] pixels, byte[] bits, int ofs, int stride); + +/** +* +* Gets the # of bits per pixel for the tile format. +* +**/ + + public int getBitsPerPixel() { + return bitsPerPixel; + } + +/** +* +* Gets the # of bytes per row (8 pixels) for the tile format. +* +**/ + + public int getBytesPerRow() { + return bytesPerRow; + } + +/** +* +* +* +**/ +/* + public long getColorCount() { + return colorCount; + } +*/ +// TEMP!!!!!!!!!! + public int getColorCount() { + if (bitsPerPixel < 8) return (1 << bitsPerPixel); + return 256; + } + +/** +* +* Gets the size in bytes of one tile encoded in this format. +* +**/ + + public int getTileSize() { + return tileSize; + } + +/** +* +* Gets the description of the codec. +* +**/ + + public String getDescription() { + return description; + } + +/** +* +* Gets the codec id. +* +**/ + + public String getID() { + return id; + } + + public String toString() { + return description; + } + +} \ No newline at end of file diff --git a/src/tm/tilecodecs/_3BPPLinearTileCodec.java b/src/tm/tilecodecs/_3BPPLinearTileCodec.java new file mode 100644 index 0000000..3df5c76 --- /dev/null +++ b/src/tm/tilecodecs/_3BPPLinearTileCodec.java @@ -0,0 +1,70 @@ +package tm.tilecodecs; + +public class _3BPPLinearTileCodec extends TileCodec { + +/** +* Constructor. +**/ + + public _3BPPLinearTileCodec() { + super("LN99", 3, "3bpp linear"); + } + +/** +* +* Decodes a tile. +* +**/ + + public int[] decode(byte[] bits, int ofs, int stride) { + int pos=0; + int b1, b2, b3; + stride *= bytesPerRow; + for (int i=0; i<8; i++) { + // do one row + b1 = bits[ofs++] & 0xFF; // byte 1: 0001 1122 + b2 = bits[ofs++] & 0xFF; // byte 2: 2333 4445 + b3 = bits[ofs++] & 0xFF; // byte 3: 5566 6777 + pixels[pos++] = (b1 >> 5) & 7; + pixels[pos++] = (b1 >> 2) & 7; + pixels[pos++] = ((b1 & 3) << 1) | ((b2 >> 7) & 1); + pixels[pos++] = (b2 >> 4) & 7; + pixels[pos++] = (b2 >> 1) & 7; + pixels[pos++] = ((b2 & 1) << 2) | ((b3 >> 6) & 3); + pixels[pos++] = (b3 >> 3) & 7; + pixels[pos++] = b3 & 7; + ofs += stride; + } + return pixels; + } + +/** +* +* Encodes a tile. +* +**/ + + public void encode(int[] pixels, byte[] bits, int ofs, int stride) { + int pos = 0; + int b1, b2, b3; + stride *= bytesPerRow; + for (int i=0; i<8; i++) { + // do one row + b1 = (pixels[pos++] & 7) << 5; + b1 |= (pixels[pos++] & 7) << 2; + b1 |= (pixels[pos] & 6) >> 1; + b2 = (pixels[pos++] & 1) << 7; + b2 |= (pixels[pos++] & 7) << 4; + b2 |= (pixels[pos++] & 7) << 1; + b2 |= (pixels[pos] & 4) >> 2; + b3 = (pixels[pos++] & 3) << 6; + b3 |= (pixels[pos++] & 7) << 3; + b3 |= (pixels[pos++] & 7); + bits[ofs++] = (byte)b1; // byte 1: 0001 1122 + bits[ofs++] = (byte)b2; // byte 2: 2333 4445 + bits[ofs++] = (byte)b3; // byte 3: 5566 6777 + ofs += stride; + } + } + +} \ No newline at end of file diff --git a/src/tm/treenodes/BookmarkItemNode.java b/src/tm/treenodes/BookmarkItemNode.java new file mode 100644 index 0000000..07ac57f --- /dev/null +++ b/src/tm/treenodes/BookmarkItemNode.java @@ -0,0 +1,256 @@ +/* +* +* 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.treenodes; + +import tm.tilecodecs.TileCodec; + +/** +* +* A file bookmark. +* Records all info required to restore the view presented to the user at the +* time the bookmark was added. +* +**/ + +public class BookmarkItemNode extends TMTreeNode { + + private int offset; // file offset + private int cols; + private int rows; + private int blockWidth; + private int blockHeight; + private boolean rowInterleaved; + private boolean sizeBlockToCanvas; + private int mode; // MODE_1D or MODE_2D + private TileCodec codec; + private String description; // entered by the user to describe/name the bookmark + private String paletteID; + private int palIndex; + + public BookmarkItemNode(int offset, + int cols, + int rows, + int blockWidth, + int blockHeight, + boolean rowInterleaved, + boolean sizeBlockToCanvas, + int mode, + int palIndex, + TileCodec codec, + String description + ) { + super(); + this.offset = offset; + this.cols = cols; + this.rows = rows; + this.blockWidth = blockWidth; + this.blockHeight = blockHeight; + this.rowInterleaved = rowInterleaved; + this.sizeBlockToCanvas = sizeBlockToCanvas; + this.mode = mode; + this.palIndex = palIndex; + this.codec = codec; + this.description = description; + } + +/** +* +* Gets the file offset that marks the beginning of the bookmark. +* +**/ + + public int getOffset() { + return offset; + } + +/** +* +* Gets the # of columns. +* +**/ + + public int getCols() { + return cols; + } + +/** +* +* Gets the # of rows. +* +**/ + + public int getRows() { + return rows; + } + +/** +* +* Gets the block width. +* +**/ + + public int getBlockWidth() { + return blockWidth; + } + +/** +* +* Gets the block height. +* +**/ + + public int getBlockHeight() { + return blockHeight; + } + +/** +* +* +* +**/ + + public boolean getRowInterleaved() { + return rowInterleaved; + } + +/** +* +* +* +**/ + + public boolean getSizeBlockToCanvas() { + return sizeBlockToCanvas; + } + +/** +* +* Gets the mode. +* +**/ + + public int getMode() { + return mode; + } + +/** +* +* Gets the palette index. +* +**/ + + public int getPalIndex() { + return palIndex; + } + +/** +* +* Gets the codec. +* +**/ + + public TileCodec getCodec() { + return codec; + } + +/** +* +* Gets the bookmark description. +* +**/ + + public String getDescription() { + return description; + } + +/** +* +* Sets the bookmark description. +* +**/ + + public void setDescription(String description) { + this.description = description; + setModified(true); + } + +/** +* +* Returns the XML-equivalent of this bookmark. +* +**/ + + public String toXML() { + StringBuffer s = new StringBuffer(); + s.append(getIndent()); + s.append("\n"); + s.append(getIndent()).append(" "); + s.append("").append(description).append("\n"); + s.append(getIndent()); + s.append("\n"); + return s.toString(); + } + +/** +* +* +* +**/ + + public String toString() { + return description; + } + +/** +* +* +* +**/ + + public void setText(String text) { + this.description = text; + } + +/** +* +* +* +**/ + + public String getToolTipText() { + return "Offset: "+offset+" ... Columns: "+cols+" ... Rows: "+rows+" ... Codec: "+codec.getDescription(); + } + +} \ No newline at end of file diff --git a/src/tm/treenodes/FolderNode.java b/src/tm/treenodes/FolderNode.java new file mode 100644 index 0000000..8b0a196 --- /dev/null +++ b/src/tm/treenodes/FolderNode.java @@ -0,0 +1,86 @@ +/* +* +* 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.treenodes; + +/** +* +* A folder. +* Has a name and zero or more children, each of which may be either +* an item or a folder. +* +**/ + +public class FolderNode extends TMTreeNode { + + private String name; + +/** +* +* Creates a folder with the given name. +* +**/ + + public FolderNode(String name) { + super(); + this.name = name; + } + +/** +* +* +* +**/ + + public String toString() { + return name; + } + +/** +* +* Returns the XML-equivalent of this folder. +* +**/ + + public String toXML() { + // TODO: Use getDepth() to indent properly + StringBuffer s = new StringBuffer(); + s.append(getIndent()); + s.append("\n"); + s.append(getIndent()).append(" "); + s.append("").append(name).append("\n"); + TMTreeNode[] children = getChildren(); + for (int i=0; i\n"); + return s.toString(); + } + +/** +* +* +* +**/ + + public void setText(String text) { + this.name = text; + } + +} \ No newline at end of file diff --git a/src/tm/treenodes/PaletteItemNode.java b/src/tm/treenodes/PaletteItemNode.java new file mode 100644 index 0000000..2a855c8 --- /dev/null +++ b/src/tm/treenodes/PaletteItemNode.java @@ -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.treenodes; + +import tm.TMPalette; +import tm.colorcodecs.ColorCodec; +import tm.utils.HexStringConverter; + +/** +* +* A palette node. +* +**/ + +public class PaletteItemNode extends TMTreeNode { + + private TMPalette palette; + private String description; + + public PaletteItemNode(TMPalette palette, String description) { + super(); + this.palette = palette; + this.description = description; + } + +/** +* +* Gets the palette represented by this node. +* +**/ + + public TMPalette getPalette() { + return palette; + } + +/** +* +* Gets the palette description. +* +**/ + + public String getDescription() { + return description; + } + +/** +* +* Sets the palette description. +* +**/ + + public void setDescription(String description) { + this.description = description; + setModified(true); + } + +/** +* +* Returns the XML-equivalent of this palette node. +* +**/ + + public String toXML() { + StringBuffer s = new StringBuffer(); + s.append(getIndent()); + s.append("\n"); + s.append(getIndent()).append(" "); + s.append("").append(description).append("\n"); + if (palette.isDirect()) { + s.append(getIndent()).append(" "); + s.append(""); + s.append(HexStringConverter.bytesToHexString(palette.entriesToBytes())); + s.append("\n"); + } + s.append(getIndent()); + s.append("\n"); + return s.toString(); + } + +/** +* +* Returns the string representation of this node. +* +**/ + + public String toString() { + return description; + } + +/** +* +* Sets the text of this node. +* +**/ + + public void setText(String text) { + setDescription(text); + } + +/** +* +* Gets the tooltiptext of this node. +* +**/ + + public String getToolTipText() { + return "Size: "+palette.getSize()+" ... Format: "+palette.getCodec().getDescription(); + } + +} \ No newline at end of file diff --git a/src/tm/treenodes/TMTreeNode.java b/src/tm/treenodes/TMTreeNode.java new file mode 100644 index 0000000..866dcea --- /dev/null +++ b/src/tm/treenodes/TMTreeNode.java @@ -0,0 +1,95 @@ +/* +* +* 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.treenodes; + +import java.util.Enumeration; +import javax.swing.tree.DefaultMutableTreeNode; + +/** +* +* Generic treenode class. +* +**/ + +public abstract class TMTreeNode extends DefaultMutableTreeNode { + + private boolean modified; + private TMTreeNode parent; + +/** +* +* Creates a treenode. +* +**/ + + public TMTreeNode() { + super(); + modified = false; + } + +/** +* +* +* +**/ + + public TMTreeNode[] getChildren() { + TMTreeNode[] ch = new TMTreeNode[getChildCount()]; + for (int i=0; irootNode. +* @param loadLeaves If true, all nodes are loaded; if false, only internal nodes. +* +**/ + + public void loadTreeNodes(TMTreeNode rootNode, boolean loadLeaves) { + if (loadLeaves) { + setModel(new DefaultTreeModel(rootNode)); + } + else { + setModel(new FolderTreeModel(rootNode)); + } +// model.addTreeModelListener(this); + } + +/** +* +* Gets the selected node. +* If there is no selected node, the root is returned as default. +* +**/ + + public TMTreeNode getSelectedNode() { + TMTreeNode n = (TMTreeNode)getLastSelectedPathComponent(); + if (n != null) return n; + return (TMTreeNode)getModel().getRoot(); + } + +/** +* +* Expands the specified node. +* +**/ + + public void expandNode(DefaultMutableTreeNode node) { + setExpandedState(new TreePath(node.getPath()), true); + } + +/** +* +* +* +**/ + + private class FolderTreeModel extends DefaultTreeModel { + + private DefaultMutableTreeNode mRoot; + + FolderTreeModel(DefaultMutableTreeNode root) { + super(root); + mRoot= root; + } + + public Object getChild(Object parent, int index) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)parent; + int pos = 0; + for (int i=0, cnt=0; i 8) + bitDepth = 8; + this.bitDepth = bitDepth; + colorCount = 1 << bitDepth; + } + +/** +* +* Paints the palette. +* +**/ + + public void paintComponent(Graphics g) { + super.paintComponent(g); + if (palette != null) { + int colorIndex = palIndex * colorCount; + int w = getBoxWidth(); + int h = getBoxHeight(); + // draw color boxes + int x=0, y=0; + for (int i=0; i ("+x2+","+y2+") = ("+w+","+h+")"); + } + +/** +* +* Sets the text that indicates the graphics codec in use. +* +**/ + + public void setCodec(String s) { + codecLabel.setText(" "+s+" "); // i18n + } + + + +/** +* +* Sets the text that indicates the graphics codec in use. +* +**/ + + public void setPalOffset(int offset) { + String hexOffset = Integer.toHexString(offset).toUpperCase(); + while (hexOffset.length() < 8) { + hexOffset = "0" + hexOffset; + } + palOffsetLabel.setText(" Palette: "+hexOffset+" "); // i18n + } + + +/** +* +* Sets the text that indicates the current mode. +* +**/ + + public void setMode(int mode) { + if (mode == TileCodec.MODE_1D) + modeLabel.setText(" 1-Dimensional "); // i18n + else + modeLabel.setText(" 2-Dimensional "); // i18n + } + +/** +* +* Sets the text that indicates how many tiles are shown. +* +**/ + + public void setTiles(int w, int h) { + tilesLabel.setText(" "+w+"x"+h+" tiles "); // i18n + } + +/** +* +* Called when a view has been selected. +* +**/ + + public void viewSelected(TMView view) { + TMEditorCanvas ec = view.getEditorCanvas(); + setMessage(""); + setOffset(view.getOffset()); + if (ec.isSelecting()) { + setSelectionCoords(ec.getSelX1(), ec.getSelY1(), ec.getCurrentCol(), ec.getCurrentRow()); + } + else if (ec.isDrawingLine()) { + setSelectionCoords(ec.getLineX1(), ec.getLineY1(), ec.getLineX2(), ec.getLineY2()); + } + else { + setCoords(ec.getCurrentCol(), ec.getCurrentRow()); + } + if (view.getTileCodec() != null) { + setCodec(view.getTileCodec().getDescription()); + } + else { + setCodec(""); + } + setPalOffset(view.getPalette().getOffset()); + setMode(view.getMode()); + setTiles(view.getCols(), view.getRows()); + } + +/** + * + * Convenience method for setting various fields of gridbagconstraints. + * + */ + + protected static void buildConstraints(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int wx, int wy) { + gbc.gridx = gx; + gbc.gridy = gy; + gbc.gridwidth = gw; + gbc.gridheight = gh; + gbc.weightx = wx; + gbc.weighty = wy; + } + +} \ No newline at end of file diff --git a/src/tm/ui/TMUI.java b/src/tm/ui/TMUI.java new file mode 100644 index 0000000..98718a4 --- /dev/null +++ b/src/tm/ui/TMUI.java @@ -0,0 +1,4833 @@ +/* +* +* 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.ui; + +import tm.*; +import tm.colorcodecs.*; +import tm.tilecodecs.*; +import tm.fileselection.*; +import tm.modaldialog.*; +import tm.treenodes.*; +import tm.utils.*; +import tm.threads.*; +import tm.filelistener.*; +import tm.canvases.*; +import javax.swing.*; +import javax.swing.event.*; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import javax.swing.border.*; +import javax.swing.filechooser.FileFilter; +import java.util.Locale; +import java.util.Vector; +import java.util.Hashtable; +import java.util.StringTokenizer; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** +* +* The main UI window. +* Has a desktop for child frames, a menu, toolbars, a palette panel, and a statusbar. +* The code is mainly dominated by +* 1) setting up the various menus and toolbars; and +* 2) providing action handlers for menu items and tool buttons. +* +**/ + +public class TMUI extends JFrame { + + // tool types + public static final int SELECT_TOOL = 1; + public static final int ZOOM_TOOL = 2; + public static final int PICKUP_TOOL = 3; + public static final int BRUSH_TOOL = 4; + public static final int LINE_TOOL = 5; + public static final int FILL_TOOL = 6; + public static final int REPLACE_TOOL = 7; + public static final int MOVE_TOOL = 8; + + private int tool = SELECT_TOOL; + private int previousTool; + + private int maxRecentFiles = 10; + private Vector recentFiles = new Vector(); + private Vector colorcodecs; + private Vector tilecodecs; + private Vector filefilters; + private Vector palettefilters; + private Vector filelisteners; + + private TMSelectionCanvas copiedSelection=null; + + // UI components + private mxScrollableDesktop desktop = new mxScrollableDesktop(); + private TMStatusBar statusBar = new TMStatusBar(); + private JToolBar toolBar = new JToolBar(JToolBar.HORIZONTAL); + private JToolBar toolPalette = new JToolBar(JToolBar.VERTICAL); + private JToolBar selectionToolBar = new JToolBar(JToolBar.VERTICAL); + private JToolBar navBar = new JToolBar(JToolBar.HORIZONTAL); + private JMenuBar menuBar = new JMenuBar(); + private JPanel toolPane = new JPanel(); // the drawing tools and such + private JPanel toolBarPane = new JPanel(); // the program toolbars + private JPanel bottomPane = new JPanel(); // palette and statusbar + private TMPalettePane palettePane; + + // file choosers + private TMApprovedFileOpenChooser fileOpenChooser = new TMApprovedFileOpenChooser(); + private TMApprovedFileSaveChooser fileSaveChooser = new TMApprovedFileSaveChooser(); + private TMApprovedFileOpenChooser bitmapOpenChooser = new TMApprovedFileOpenChooser(); + private TMApprovedFileSaveChooser bitmapSaveChooser = new TMApprovedFileSaveChooser(); + private TMApprovedFileOpenChooser paletteOpenChooser = new TMApprovedFileOpenChooser(); + + private TMBitmapFilters bmf = new TMBitmapFilters(); + private TMFileFilter allFilter = new AllFilter(); + + // custom dialogs + private TMGoToDialog goToDialog; + private TMNewFileDialog newFileDialog; + private TMCustomCodecDialog customCodecDialog; + private TMStretchDialog stretchDialog; + private TMCanvasSizeDialog canvasSizeDialog; + private TMBlockSizeDialog blockSizeDialog; + private TMAddToTreeDialog addBookmarkDialog; + private TMAddToTreeDialog addPaletteDialog; + private TMOrganizeTreeDialog organizeBookmarksDialog; + private TMOrganizeTreeDialog organizePalettesDialog; + private TMNewPaletteDialog newPaletteDialog; + private TMPaletteSizeDialog paletteSizeDialog; + private TMImportInternalPaletteDialog importInternalPaletteDialog; + + // toolbar buttons + ClassLoader cl = getClass().getClassLoader(); + private ToolButton newButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/New24.gif"))); + private ToolButton openButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Open24.gif"))); + private ToolButton saveButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Save24.gif"))); + private ToolButton cutButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Cut24.gif"))); + private ToolButton copyButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Copy24.gif"))); + private ToolButton pasteButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Paste24.gif"))); + private ToolButton undoButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Undo24.gif"))); + private ToolButton redoButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Redo24.gif"))); + private ToolButton gotoButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Import24.gif"))); + private ToolButton addBookmarkButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Bookmarks24.gif"))); + private ToolButton decWidthButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/DecWidth24.gif"))); + private ToolButton incWidthButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/IncWidth24.gif"))); + private ToolButton decHeightButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/DecHeight24.gif"))); + private ToolButton incHeightButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/IncHeight24.gif"))); + + // navigation bar buttons + private ToolButton minusPageButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Rewind24.gif"))); + private ToolButton minusRowButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/StepBack24.gif"))); + private ToolButton minusTileButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Back24.gif"))); + private ToolButton minusByteButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Minus24.gif"))); + private ToolButton plusByteButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Plus24.gif"))); + private ToolButton plusTileButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Forward24.gif"))); + private ToolButton plusRowButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/StepForward24.gif"))); + private ToolButton plusPageButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/FastForward24.gif"))); + + // tool palette buttons + private ToolToggleButton selectButton = new ToolToggleButton(new ImageIcon(cl.getResource("tm/icons/Selection24.gif"))); + private ToolToggleButton zoomButton = new ToolToggleButton(new ImageIcon(cl.getResource("tm/icons/Zoom24.gif"))); + private ToolToggleButton pickupButton = new ToolToggleButton(new ImageIcon(cl.getResource("tm/icons/Dropper24.gif"))); + private ToolToggleButton brushButton = new ToolToggleButton(new ImageIcon(cl.getResource("tm/icons/Brush24.gif"))); + private ToolToggleButton lineButton = new ToolToggleButton(new ImageIcon(cl.getResource("tm/icons/Line24.gif"))); + private ToolToggleButton fillButton = new ToolToggleButton(new ImageIcon(cl.getResource("tm/icons/Fill24.gif"))); + private ToolToggleButton replaceButton = new ToolToggleButton(new ImageIcon(cl.getResource("tm/icons/ColorReplacer24.gif"))); + private ToolToggleButton moveButton = new ToolToggleButton(new ImageIcon(cl.getResource("tm/icons/Mover24.gif"))); + + // selection palette buttons + private ToolButton mirrorButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Mirror24.gif"))); + private ToolButton flipButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/Flip24.gif"))); + private ToolButton rotateRightButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/RotateRight24.gif"))); + private ToolButton rotateLeftButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/RotateLeft24.gif"))); + private ToolButton shiftLeftButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/ShiftLeft24.gif"))); + private ToolButton shiftRightButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/ShiftRight24.gif"))); + private ToolButton shiftUpButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/ShiftUp24.gif"))); + private ToolButton shiftDownButton = new ToolButton(new ImageIcon(cl.getResource("tm/icons/ShiftDown24.gif"))); + + // File menu + private JMenu fileMenu = new JMenu("File"); + private JMenuItem newMenuItem = new JMenuItem("New..."); + private JMenuItem openMenuItem = new JMenuItem("Open..."); + private JMenu reopenMenu = new JMenu("Reopen"); + private JMenuItem closeMenuItem = new JMenuItem("Close"); + private JMenuItem closeAllMenuItem = new JMenuItem("Close All"); + private JMenuItem saveMenuItem = new JMenuItem("Save"); + private JMenuItem saveAsMenuItem = new JMenuItem("Save As..."); + private JMenuItem saveAllMenuItem = new JMenuItem("Save All"); + private JMenuItem exitMenuItem = new JMenuItem("Exit"); + // Edit menu + private JMenu editMenu = new JMenu("Edit"); + private JMenuItem undoMenuItem = new JMenuItem("Undo"); + private JMenuItem redoMenuItem = new JMenuItem("Redo"); + private JMenuItem cutMenuItem = new JMenuItem("Cut"); + private JMenuItem copyMenuItem = new JMenuItem("Copy"); + private JMenuItem pasteMenuItem = new JMenuItem("Paste"); + private JMenuItem clearMenuItem = new JMenuItem("Clear"); + private JMenuItem selectAllMenuItem = new JMenuItem("Select All"); + private JMenuItem copyToMenuItem = new JMenuItem("Copy To..."); + private JMenuItem pasteFromMenuItem = new JMenuItem("Paste From..."); + // Image menu + private JMenu imageMenu = new JMenu("Image"); + private JMenuItem mirrorMenuItem = new JMenuItem("Mirror"); + private JMenuItem flipMenuItem = new JMenuItem("Flip"); + private JMenuItem rotateRightMenuItem = new JMenuItem("Rotate Right"); + private JMenuItem rotateLeftMenuItem = new JMenuItem("Rotate Left"); + private JMenuItem shiftLeftMenuItem = new JMenuItem("Shift Left"); + private JMenuItem shiftRightMenuItem = new JMenuItem("Shift Right"); + private JMenuItem shiftUpMenuItem = new JMenuItem("Shift Up"); + private JMenuItem shiftDownMenuItem = new JMenuItem("Shift Down"); + private JMenuItem canvasSizeMenuItem = new JMenuItem("Canvas Size..."); + private JMenuItem stretchMenuItem = new JMenuItem("Stretch..."); + // View menu + private JMenu viewMenu = new JMenu("View"); + private JCheckBoxMenuItem statusBarMenuItem = new JCheckBoxMenuItem("Statusbar"); + private JCheckBoxMenuItem toolBarMenuItem = new JCheckBoxMenuItem("Toolbar"); + private JMenu tileCodecMenu = new JMenu("Codec"); + private JMenu zoomMenu = new JMenu("Zoom"); + private JMenuItem zoomInMenuItem = new JMenuItem("In"); + private JMenuItem zoomOutMenuItem = new JMenuItem("Out"); + private JMenuItem _100MenuItem = new JMenuItem("100%"); + private JMenuItem _200MenuItem = new JMenuItem("200%"); + private JMenuItem _400MenuItem = new JMenuItem("400%"); + private JMenuItem _800MenuItem = new JMenuItem("800%"); + private JMenuItem _1600MenuItem = new JMenuItem("1600%"); + private JMenuItem _3200MenuItem = new JMenuItem("3200%"); + private JMenu blockSizeMenu = new JMenu("Block Size"); + private JCheckBoxMenuItem sizeBlockToCanvasMenuItem = new JCheckBoxMenuItem("Full Canvas"); + private JMenuItem customBlockSizeMenuItem = new JMenuItem("Custom..."); + private JRadioButtonMenuItem rowInterleaveBlocksMenuItem = new JRadioButtonMenuItem("Row-interleave Blocks"); + private JMenu modeMenu = new JMenu("Mode"); + private JRadioButtonMenuItem _1DimensionalMenuItem = new JRadioButtonMenuItem("1-Dimensional"); + private JRadioButtonMenuItem _2DimensionalMenuItem = new JRadioButtonMenuItem("2-Dimensional"); + private JCheckBoxMenuItem blockGridMenuItem = new JCheckBoxMenuItem("Block Grid"); + private JCheckBoxMenuItem tileGridMenuItem = new JCheckBoxMenuItem("Tile Grid"); + private JCheckBoxMenuItem pixelGridMenuItem = new JCheckBoxMenuItem("Pixel Grid"); + // Navigate menu + private JMenu navigateMenu = new JMenu("Navigate"); + private JMenuItem goToMenuItem = new JMenuItem("Go To..."); + private JMenuItem goToAgainMenuItem = new JMenuItem("Go To Again"); + private JMenuItem addToBookmarksMenuItem = new JMenuItem("Add To Bookmarks..."); + private JMenuItem organizeBookmarksMenuItem = new JMenuItem("Organize Bookmarks..."); + // private JMenuItem saveBookmarksMenuItem = new JMenuItem("Save Bookmarks"); + // Palette menu + private JMenu paletteMenu = new JMenu("Palette"); + private JMenuItem editColorsMenuItem = new JMenuItem("Edit Colors..."); + private JMenu colorCodecMenu = new JMenu("Format"); + private JMenu paletteEndiannessMenu = new JMenu("Endianness"); + private JRadioButtonMenuItem paletteLittleEndianMenuItem = new JRadioButtonMenuItem("Little"); + private JRadioButtonMenuItem paletteBigEndianMenuItem = new JRadioButtonMenuItem("Big"); + private JRadioButtonMenuItem dummyPaletteMenuItem = new JRadioButtonMenuItem(); + private JMenuItem paletteSizeMenuItem = new JMenuItem("Size..."); + private JMenuItem newPaletteMenuItem = new JMenuItem("New..."); + private JMenu importPaletteMenu = new JMenu("Import From"); + private JMenuItem importInternalPaletteMenuItem = new JMenuItem("This File..."); + private JMenuItem importExternalPaletteMenuItem = new JMenuItem("Another File..."); + private JMenuItem addToPalettesMenuItem = new JMenuItem("Add To Palettes..."); + private JMenuItem organizePalettesMenuItem = new JMenuItem("Organize Palettes..."); +// private JMenuItem savePalettesMenuItem = new JMenuItem("Save Palettes"); +// private JMenuItem exportPaletteMenuItem = new JMenuItem("Export..."); // tpl, c, asm, java? + // Window menu + private JMenu windowMenu = new JMenu("Window"); + private JMenuItem newWindowMenuItem = new JMenuItem("New Window"); + private JMenuItem tileMenuItem = new JMenuItem("Tile"); + private JMenuItem cascadeMenuItem = new JMenuItem("Cascade"); + private JMenuItem arrangeIconsMenuItem = new JMenuItem("Arrange Icons"); + // Help menu + private JMenu helpMenu = new JMenu("Help"); + private JMenuItem helpTopicsMenuItem = new JMenuItem("Help Topics"); + private JMenuItem tipMenuItem = new JMenuItem("Tip of the Millennium..."); + private JMenuItem aboutMenuItem = new JMenuItem("About Tile Molester..."); + + // button groups + private ButtonGroup toolButtonGroup = new ButtonGroup(); + private ButtonGroup colorCodecButtonGroup = new ButtonGroup(); + private ButtonGroup tileCodecButtonGroup = new ButtonGroup(); + private ButtonGroup paletteButtonGroup = new ButtonGroup(); + private ButtonGroup modeButtonGroup = new ButtonGroup(); + private ButtonGroup paletteEndiannessButtonGroup = new ButtonGroup(); + + private Hashtable tileCodecButtonHashtable = new Hashtable(); + private Hashtable colorCodecButtonHashtable = new Hashtable(); + private Hashtable paletteButtonHashtable = new Hashtable(); + private Hashtable fileListenerHashtable = new Hashtable(); + + private Xlator xl; + private File settingsFile = new File("settings.xml"); + + private Locale locale = Locale.getDefault(); + private boolean viewStatusBar=true; + private boolean viewToolBar=true; + + private String lastPath = ""; + + +/** +* +* Creates a Tile Molester UI. +* +**/ + + public TMUI() { + + super("Tile Molester"); + + //I tried to change the UI but it seems innatural + /*try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + }*/ + + setIconImage(new ImageIcon(cl.getResource("tm/icons/TMIcon.gif")).getImage()); + + if (settingsFile.exists()) { + // load settings from file + loadSettings(); + } + else { + // assume this is the first time program is run + selectLanguage(); + // File bookmarksDir = new File("bookmarks"); + // bookmarksDir.mkdir(); + // copy bookmarks.dtd to bookmarksDir + // File palettesDir = new File("palettes"); + // palettesDir.mkdir(); + // copy palettes.dtd to palettesDir +/* File resourcesDir = new File("resources"); + if (!resourcesDir.exists()) { + resourcesDir.mkdir(); + File resourceDTDFile = new File(resourcesDir, "tmres.dtd"); + try { + FileWriter fw = new FileWriter(resourceDTDFile); + fw.write(""+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + "\n"+ + ""); + fw.close(); + } + catch (Exception e) { } + }*/ + } + setLocale(locale); + Locale.setDefault(locale); + JComponent.setDefaultLocale(this.locale); + + // show splash screen + new TMSplashScreen(this); + +// create a translator + try { + xl = new Xlator("languages/language", locale); + } catch (Exception e) { + JOptionPane.showMessageDialog(this, + xlate("Error reading language file:")+"\n"+e.getMessage(), + "Tile Molester", + JOptionPane.ERROR_MESSAGE); + System.exit(0); + } + +///////// Translate items + + // File menu + fileMenu.setText(xlate("File")); + newMenuItem.setText(xlate("New")); + openMenuItem.setText(xlate("Open")); + reopenMenu.setText(xlate("Reopen")); + closeMenuItem.setText(xlate("Close")); + closeAllMenuItem.setText(xlate("Close_All")); + saveMenuItem.setText(xlate("Save")); + saveAsMenuItem.setText(xlate("Save_As")); + saveAllMenuItem.setText(xlate("Save_All")); + exitMenuItem.setText(xlate("Exit")); + // Edit menu + editMenu.setText(xlate("Edit")); + undoMenuItem.setText(xlate("Undo")); + redoMenuItem.setText(xlate("Redo")); + cutMenuItem.setText(xlate("Cut")); + copyMenuItem.setText(xlate("Copy")); + pasteMenuItem.setText(xlate("Paste")); + clearMenuItem.setText(xlate("Clear")); + selectAllMenuItem.setText(xlate("Select_All")); + copyToMenuItem.setText(xlate("Copy_To")); + pasteFromMenuItem.setText(xlate("Paste_From")); + pasteFromMenuItem.setText(xlate("New_Selection")); + pasteFromMenuItem.setText(xlate("Apply_Selection")); + // Image menu + imageMenu.setText(xlate("Image")); + mirrorMenuItem.setText(xlate("Mirror")); + flipMenuItem.setText(xlate("Flip")); + rotateRightMenuItem.setText(xlate("Rotate_Right")); + rotateLeftMenuItem.setText(xlate("Rotate_Left")); + shiftLeftMenuItem.setText(xlate("Shift_Left")); + shiftRightMenuItem.setText(xlate("Shift_Right")); + shiftUpMenuItem.setText(xlate("Shift_Up")); + shiftDownMenuItem.setText(xlate("Shift_Down")); + canvasSizeMenuItem.setText(xlate("Canvas_Size")); + stretchMenuItem.setText(xlate("Stretch")); + // View menu + viewMenu.setText(xlate("View")); + statusBarMenuItem.setText(xlate("Statusbar")); + toolBarMenuItem.setText(xlate("Toolbar")); + tileCodecMenu.setText(xlate("Codec")); + zoomMenu.setText(xlate("Zoom")); + zoomInMenuItem.setText(xlate("In")); + zoomOutMenuItem.setText(xlate("Out")); + _100MenuItem.setText(xlate("100%")); + _200MenuItem.setText(xlate("200%")); + _400MenuItem.setText(xlate("400%")); + _800MenuItem.setText(xlate("800%")); + _1600MenuItem.setText(xlate("1600%")); + _3200MenuItem.setText(xlate("3200%")); + modeMenu.setText(xlate("Mode")); + _1DimensionalMenuItem.setText(xlate("1_Dimensional")); + _2DimensionalMenuItem.setText(xlate("2_Dimensional")); + blockSizeMenu.setText(xlate("Block_Size")); + sizeBlockToCanvasMenuItem.setText(xlate("Full_Canvas")); + customBlockSizeMenuItem.setText(xlate("Custom_Block_Size")); + rowInterleaveBlocksMenuItem.setText(xlate("Row_Interleave_Blocks")); + blockGridMenuItem.setText(xlate("Block_Grid")); + tileGridMenuItem.setText(xlate("Tile_Grid")); + pixelGridMenuItem.setText(xlate("Pixel_Grid")); + // Navigate menu + navigateMenu.setText(xlate("Navigate")); + goToMenuItem.setText(xlate("Go_To")); + goToAgainMenuItem.setText(xlate("Go_To_Again")); + addToBookmarksMenuItem.setText(xlate("Add_To_Bookmarks")); + organizeBookmarksMenuItem.setText(xlate("Organize_Bookmarks")); + // Palette menu + paletteMenu.setText(xlate("Palette")); + editColorsMenuItem.setText(xlate("Edit_Colors")); + colorCodecMenu.setText(xlate("Format")); + paletteEndiannessMenu.setText(xlate("Endianness")); + paletteLittleEndianMenuItem.setText(xlate("Little_Endian")); + paletteBigEndianMenuItem.setText(xlate("Big_Endian")); + paletteSizeMenuItem.setText(xlate("Size")); + newPaletteMenuItem.setText(xlate("New")); + importPaletteMenu.setText(xlate("Import_From")); + importInternalPaletteMenuItem.setText(xlate("This_File")); + importExternalPaletteMenuItem.setText(xlate("Another_File")); + addToPalettesMenuItem.setText(xlate("Add_To_Palettes")); + organizePalettesMenuItem.setText(xlate("Organize_Palettes")); + // Window menu + windowMenu.setText(xlate("Window")); + newWindowMenuItem.setText(xlate("New_Window")); + tileMenuItem.setText(xlate("Tile")); + cascadeMenuItem.setText(xlate("Cascade")); + arrangeIconsMenuItem.setText(xlate("Arrange_Icons")); + // Help menu + helpMenu.setText(xlate("Help")); + helpTopicsMenuItem.setText(xlate("Help_Topics")); + tipMenuItem.setText(xlate("Tip_of_the_Millennium")); + aboutMenuItem.setText(xlate("About_Tile_Molester")); + + UIManager.put("OptionPane.yesButtonText", xlate("Yes")); + UIManager.put("OptionPane.noButtonText", xlate("No")); + UIManager.put("OptionPane.cancelButtonText", xlate("Cancel")); + UIManager.put("OptionPane.okButtonText", xlate("OK")); + + fileOpenChooser.setDialogTitle(xlate("Open_File_Dialog_Title")); + fileSaveChooser.setDialogTitle(xlate("Save_As_Dialog_Title")); + bitmapOpenChooser.setDialogTitle(xlate("Paste_From_Dialog_Title")); + bitmapSaveChooser.setDialogTitle(xlate("Copy_To_Dialog_Title")); + paletteOpenChooser.setDialogTitle(xlate("Open_Palette_Dialog_Title")); + +///////// Read specs + try { + TMSpecReader.readSpecsFromFile(new File("tmspec.xml")); + } + catch (SAXParseException e) { + JOptionPane.showMessageDialog(this, + xlate("Parser_Parse_Error")+"\n"+ + e.getMessage()+"\n"+ + "("+e.getSystemId()+",\n"+ + "line "+e.getLineNumber()+")\n", + "Tile Molester", + JOptionPane.ERROR_MESSAGE); + System.exit(0); + } + catch (SAXException e) { + JOptionPane.showMessageDialog(this, + xlate("Parser_Parse_Error")+"\n"+e.getMessage(), + "Tile Molester", + JOptionPane.ERROR_MESSAGE); + System.exit(0); + } + catch (ParserConfigurationException e) { + JOptionPane.showMessageDialog(this, + xlate("Parser_Config_Error")+"\n"+e.getMessage(), + "Tile Molester", + JOptionPane.ERROR_MESSAGE); + System.exit(0); + } + catch (IOException e) { + JOptionPane.showMessageDialog(this, + xlate("Parser_IO_Error")+"\n"+e.getMessage(), + "Tile Molester", + JOptionPane.ERROR_MESSAGE); + System.exit(0); + } + + colorcodecs = TMSpecReader.getColorCodecs(); + tilecodecs = TMSpecReader.getTileCodecs(); + filefilters = TMSpecReader.getFileFilters(); + palettefilters = TMSpecReader.getPaletteFilters(); + filelisteners = TMSpecReader.getFileListeners(); + + tilecodecs.add(new _3BPPLinearTileCodec()); +////////// + + // create dialogs. + goToDialog = new TMGoToDialog(this, xl); + newFileDialog = new TMNewFileDialog(this, xl); +// customCodecDialog = new TMCustomCodecDialog(this, "Custom Codec", true, xl); + stretchDialog = new TMStretchDialog(this, xl); + canvasSizeDialog = new TMCanvasSizeDialog(this, xl); + blockSizeDialog = new TMBlockSizeDialog(this, xl); + addBookmarkDialog = new TMAddToTreeDialog(this, "Add_To_Bookmarks_Dialog_Title", xl); + addPaletteDialog = new TMAddToTreeDialog(this, "Add_To_Palettes_Dialog_Title", xl); + organizeBookmarksDialog = new TMOrganizeTreeDialog(this, "Organize_Bookmarks_Dialog_Title", xl); + organizePalettesDialog = new TMOrganizeTreeDialog(this, "Organize_Palettes_Dialog_Title", xl); + newPaletteDialog = new TMNewPaletteDialog(this, xl); + paletteSizeDialog = new TMPaletteSizeDialog(this, xl); + importInternalPaletteDialog = new TMImportInternalPaletteDialog(this, xl); + + newPaletteDialog.setCodecs(colorcodecs); + importInternalPaletteDialog.setCodecs(colorcodecs); + + //Set up the GUI. + // main contentpane + JPanel pane = new JPanel(); + setContentPane(pane); + pane.setDoubleBuffered(true); + pane.setLayout(new BorderLayout()); + + // main toolbar + initToolBar(); + initNavBar(); + toolBarPane.setLayout(new FlowLayout(FlowLayout.LEFT)); + toolBarPane.add(toolBar); + toolBarPane.add(navBar); + pane.add(toolBarPane, BorderLayout.NORTH); + + // desktop + pane.add(new JScrollPane(desktop), BorderLayout.CENTER); + + // palette pane & statusbar + palettePane = new TMPalettePane(this); + statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED)); + bottomPane.setLayout(new BorderLayout()); + bottomPane.add(palettePane, BorderLayout.CENTER); + bottomPane.add(statusBar, BorderLayout.SOUTH); + pane.add(bottomPane, BorderLayout.SOUTH); + + // tool palettes + initToolPalette(); + initSelectionToolBar(); + JPanel barPane = new JPanel(); + barPane.setLayout(new GridLayout(1, 2)); + barPane.add(selectionToolBar); + barPane.add(toolPalette); + toolPane.setLayout(new BorderLayout()); + toolPane.add(barPane, BorderLayout.NORTH); + pane.add(toolPane, BorderLayout.WEST); + + // menus + initMenuBar(); + setJMenuBar(menuBar); + buildReopenMenu(); + + initTileCodecUIStuff(); + buildColorCodecsMenu(); + initPaletteOpenChooser(); + + // Set up file save chooser. + fileSaveChooser.setAcceptAllFileFilterUsed(false); + fileSaveChooser.addChoosableFileFilter(allFilter); + fileSaveChooser.setFileFilter(allFilter); + + // Set up bitmap open chooser. + bitmapOpenChooser.setAcceptAllFileFilterUsed(false); + bitmapOpenChooser.addChoosableFileFilter(bmf.supported); + bitmapOpenChooser.addChoosableFileFilter(bmf.gif); + bitmapOpenChooser.addChoosableFileFilter(bmf.jpeg); + bitmapOpenChooser.addChoosableFileFilter(bmf.png); + bitmapOpenChooser.addChoosableFileFilter(bmf.bmp); + bitmapOpenChooser.addChoosableFileFilter(bmf.pcx); + bitmapOpenChooser.setFileFilter(bmf.supported); + + // Set up bitmap save chooser. + bitmapSaveChooser.setAcceptAllFileFilterUsed(false); + bitmapSaveChooser.addChoosableFileFilter(bmf.gif); + bitmapSaveChooser.addChoosableFileFilter(bmf.jpeg); + bitmapSaveChooser.addChoosableFileFilter(bmf.png); + bitmapSaveChooser.addChoosableFileFilter(bmf.bmp); + bitmapSaveChooser.addChoosableFileFilter(bmf.pcx); + bitmapSaveChooser.setFileFilter(bmf.png); + + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + doExitCommand(); + } + public void windowActivated(WindowEvent e) { +// HACK to fix the GUI after running FCEU in fullscreen mode +// int state = getExtendedState(); +// setExtendedState(JFrame.ICONIFIED); +// setExtendedState(state); + } + }); + + // Center the frame + int inset = 128; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setBounds(inset, inset, + screenSize.width - inset*2, + screenSize.height-inset*2); + + //Make dragging faster: + desktop.putClientProperty("JDesktopPane.dragMode", "outline"); + + // MDI menus and such shouldn't be shown until file loaded. + disableMDIStuff(); + + toolBarPane.setVisible(viewToolBar); + + // Show and maximize. + setVisible(true); + setExtendedState(JFrame.MAXIMIZED_BOTH); + } + +/** +* +* Saves program settings to file. +* +**/ + + public void saveSettings() { + StringBuffer sb = new StringBuffer(); + sb.append("\n"); + sb.append("\n"); + sb.append("\n"); + + sb.append(makePropertyTag("locale", locale.toString())); + sb.append(makePropertyTag("viewStatusBar", ""+viewStatusBar)); + sb.append(makePropertyTag("viewToolBar", ""+viewToolBar)); + sb.append(makePropertyTag("maxRecentFiles", ""+maxRecentFiles)); + + File recentFile = null; + //To remember last path + for (int i=0; i\n"); + + // write to file + try { + FileWriter fw = new FileWriter(settingsFile); + fw.write(sb.toString()); + fw.close(); + } + catch (Exception e) { + JOptionPane.showMessageDialog(this, + xlate("Save_Settings_Error")+"\n"+e.getMessage(), + "Tile Molester", + JOptionPane.ERROR_MESSAGE); + } + } + +/** +* +* Makes a property tag with the given key and value. +* +**/ + + public String makePropertyTag(String key, String value) { + return " \n"; + } + +/** +* +* Loads program settings from file. +* +**/ + + public void loadSettings() { + Document doc = null; + try { + doc = XMLParser.parse(settingsFile); + } + catch (Exception e) { + JOptionPane.showMessageDialog(this, + xlate("Load_Settings_Error")+"\n"+e.getMessage(), + "Tile Molester", + JOptionPane.ERROR_MESSAGE); + } + if (doc == null) return; + + Element settings = doc.getDocumentElement(); + NodeList properties = settings.getElementsByTagName("property"); + // process all the properties + for (int i=0; i 0? + File resourceFile = TMFileResources.getResourceFileFor(img.getFile()); + try { + File res = new File("./resources"); + if (!res.exists()) { + res.mkdir(); + } + FileWriter fw = new FileWriter(resourceFile); + fw.write(img.getResources().toXML()); + fw.close(); + } + catch (Exception e) { + JOptionPane.showMessageDialog(this, + xlate("Save_Resources_Error")+"\n"+e.getMessage(), + "Tile Molester", + JOptionPane.ERROR_MESSAGE); + } + } + + public void saveBookmarks() { + // TODO + } + + public void savePalettes() { + // TODO + } + +/** +* +* Handles menu command "Close All". +* Does the same as "Close", only for all the current frames. +* +**/ + + public void doCloseAllCommand() { + JInternalFrame[] frames = desktop.getAllFrames(); + for (int i=0; i= 0; i--) { + if (!frames[i].isIcon()) { + frames[i].setLocation(xpos, ypos); + xpos += FRAME_OFFSET; + ypos += FRAME_OFFSET; + } + } + desktop.revalidate(); + } + +/** +* +* Handles menu command "Arrange Icons". +* +**/ + + public void doArrangeIconsCommand() { + JInternalFrame[] frames = desktop.getAllFrames(); + int xpos=0; + int ypos=0; + for (int i=0; i