mirror of
https://github.com/IllusionMan1212/gif-extractor-go.git
synced 2026-10-24 10:12:36 +00:00
write frames to png files and some clean up
This commit is contained in:
+4
-4
@@ -69,6 +69,10 @@ type ImageDescriptor struct {
|
|||||||
Packed byte // image and color table data information
|
Packed byte // image and color table data information
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
/* EXTENSION BLOCKS */
|
||||||
|
/********************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
GraphicsControlPacked {
|
GraphicsControlPacked {
|
||||||
0: TransparentColorFlag
|
0: TransparentColorFlag
|
||||||
@@ -78,10 +82,6 @@ GraphicsControlPacked {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/********************/
|
|
||||||
/* EXTENSION BLOCKS */
|
|
||||||
/********************/
|
|
||||||
|
|
||||||
// Only available on 89a and comes after the global color table and before the 1st image
|
// Only available on 89a and comes after the global color table and before the 1st image
|
||||||
type GraphicsControlBlock struct {
|
type GraphicsControlBlock struct {
|
||||||
Packed byte // method of graphics disposal to use
|
Packed byte // method of graphics disposal to use
|
||||||
|
|||||||
@@ -31,6 +31,18 @@ func (v Palette) UnmarshalBinary(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v Palette) MarshalBinary() []byte {
|
||||||
|
data := make([]byte, len(v)*3)
|
||||||
|
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
data[i*3] = v[i].Red
|
||||||
|
data[i*3+1] = v[i].Green
|
||||||
|
data[i*3+2] = v[i].Blue
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
func deinterlace() {
|
func deinterlace() {
|
||||||
// TODO:
|
// TODO:
|
||||||
}
|
}
|
||||||
@@ -59,7 +71,7 @@ func main() {
|
|||||||
fmt.Print("WARNING: some features are unimplemented for this version. Some images may not extract correctly\n\n")
|
fmt.Print("WARNING: some features are unimplemented for this version. Some images may not extract correctly\n\n")
|
||||||
}
|
}
|
||||||
fmt.Printf("GIF aspect ratio is: %v\n", header.AspectRatio)
|
fmt.Printf("GIF aspect ratio is: %v\n", header.AspectRatio)
|
||||||
fmt.Printf("GIF background color is: %v\n", header.BackgroundColor)
|
fmt.Printf("GIF background color index is: %v\n", header.BackgroundColor)
|
||||||
fmt.Printf("GIF height is: %v\n", header.ScreenHeight)
|
fmt.Printf("GIF height is: %v\n", header.ScreenHeight)
|
||||||
fmt.Printf("GIF width is: %v\n", header.ScreenWidth)
|
fmt.Printf("GIF width is: %v\n", header.ScreenWidth)
|
||||||
|
|
||||||
@@ -78,20 +90,20 @@ func main() {
|
|||||||
}
|
}
|
||||||
dirName := strings.Split(st.Name(), ".gif")[0]
|
dirName := strings.Split(st.Name(), ".gif")[0]
|
||||||
|
|
||||||
fmt.Printf("%v\n", dirName)
|
|
||||||
os.Mkdir(dirName, os.FileMode(0755))
|
os.Mkdir(dirName, os.FileMode(0755))
|
||||||
|
|
||||||
var palette Palette
|
var globalPalette Palette
|
||||||
|
var currentPalette Palette
|
||||||
|
transparencyIndex := -1
|
||||||
|
|
||||||
if GlobalColorTableFlag == 0 { // no global color table, use default one (???)
|
if GlobalColorTableFlag == 1 { // global color table exists, use it
|
||||||
// TODO: do we skip it. no idea if this is accurate
|
|
||||||
file.Seek(int64(GlobalColorTableSize), os.SEEK_CUR)
|
|
||||||
} else { // global color table exists, use it
|
|
||||||
globalColorTable := make([]byte, GlobalColorTableSize)
|
globalColorTable := make([]byte, GlobalColorTableSize)
|
||||||
file.Read(globalColorTable)
|
file.Read(globalColorTable)
|
||||||
|
|
||||||
palette = make(Palette, GlobalColorTableEntries)
|
globalPalette = make(Palette, GlobalColorTableEntries)
|
||||||
palette.UnmarshalBinary(globalColorTable)
|
globalPalette.UnmarshalBinary(globalColorTable)
|
||||||
|
|
||||||
|
currentPalette = globalPalette
|
||||||
}
|
}
|
||||||
|
|
||||||
counter := 1
|
counter := 1
|
||||||
@@ -100,16 +112,12 @@ func main() {
|
|||||||
nextByte := make([]byte, 1)
|
nextByte := make([]byte, 1)
|
||||||
file.Read(nextByte)
|
file.Read(nextByte)
|
||||||
|
|
||||||
// fmt.Printf("next byte is: 0x%x\n", nextByte[0])
|
|
||||||
|
|
||||||
switch nextByte[0] {
|
switch nextByte[0] {
|
||||||
case EXTENSION_BLOCK:
|
case EXTENSION_BLOCK:
|
||||||
{
|
{
|
||||||
nextSecondByte := make([]byte, 1)
|
nextSecondByte := make([]byte, 1)
|
||||||
file.Read(nextSecondByte)
|
file.Read(nextSecondByte)
|
||||||
|
|
||||||
// fmt.Printf("next second byte is: 0x%x\n", nextSecondByte[0])
|
|
||||||
|
|
||||||
switch nextSecondByte[0] {
|
switch nextSecondByte[0] {
|
||||||
case PLAINTEXT_BLOCK:
|
case PLAINTEXT_BLOCK:
|
||||||
{
|
{
|
||||||
@@ -124,9 +132,23 @@ func main() {
|
|||||||
}
|
}
|
||||||
case GRAPHICS_CONTROL_BLOCK:
|
case GRAPHICS_CONTROL_BLOCK:
|
||||||
{
|
{
|
||||||
file.Seek(GRAPHICS_CONTROL_BLOCK_SIZE+1, os.SEEK_CUR)
|
// skip the length byte
|
||||||
|
file.Seek(1, os.SEEK_CUR)
|
||||||
|
|
||||||
// TODO: read graphics control block and do something with it ???
|
// TODO: use the delayTime for something ???
|
||||||
|
|
||||||
|
block := GraphicsControlBlock{}
|
||||||
|
data := make([]byte, GRAPHICS_CONTROL_BLOCK_SIZE)
|
||||||
|
|
||||||
|
file.Read(data)
|
||||||
|
|
||||||
|
binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &block)
|
||||||
|
|
||||||
|
transparencyFlag := block.Packed & 1
|
||||||
|
|
||||||
|
if transparencyFlag == 1 {
|
||||||
|
transparencyIndex = int(block.TransparentColorIndex)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -168,27 +190,18 @@ func main() {
|
|||||||
SortFlag := (imageDescriptor.Packed & 0x20) >> 5
|
SortFlag := (imageDescriptor.Packed & 0x20) >> 5
|
||||||
LocalColorTableEntries := math.Pow(2, float64((imageDescriptor.Packed&7)+1))
|
LocalColorTableEntries := math.Pow(2, float64((imageDescriptor.Packed&7)+1))
|
||||||
|
|
||||||
// fmt.Printf("Local Color Table Flag: %v\n", LocalColorTableFlag)
|
// TODO: use offsets when writing image ??
|
||||||
// fmt.Printf("Interlace Flag: %v\n", InterlaceFlag)
|
|
||||||
// fmt.Printf("Sort Flag: %v\n", SortFlag)
|
|
||||||
// fmt.Printf("Local Color Table Entries: %v\n", LocalColorTableEntries)
|
|
||||||
|
|
||||||
fmt.Printf("Left Offset: %v\n", imageDescriptor.Left)
|
|
||||||
fmt.Printf("Top Offset: %v\n", imageDescriptor.Top)
|
|
||||||
|
|
||||||
// TODO: use color tables for correct colors on pixels
|
|
||||||
// TODO: write the image data to PNG
|
|
||||||
|
|
||||||
// local color table comes first
|
// local color table comes first
|
||||||
var LocalColorTable Palette
|
var localPalette Palette
|
||||||
if LocalColorTableFlag == 1 {
|
if LocalColorTableFlag == 1 {
|
||||||
tempLocalColorTable := make([]byte, int(LocalColorTableEntries*3))
|
tempLocalColorTable := make([]byte, int(LocalColorTableEntries*3))
|
||||||
file.Read(tempLocalColorTable)
|
file.Read(tempLocalColorTable)
|
||||||
|
|
||||||
LocalColorTable = make(Palette, int(LocalColorTableEntries))
|
localPalette = make(Palette, int(LocalColorTableEntries))
|
||||||
LocalColorTable.UnmarshalBinary(tempLocalColorTable)
|
localPalette.UnmarshalBinary(tempLocalColorTable)
|
||||||
|
|
||||||
// TODO: use local color table
|
currentPalette = localPalette
|
||||||
}
|
}
|
||||||
|
|
||||||
if InterlaceFlag == 1 {
|
if InterlaceFlag == 1 {
|
||||||
@@ -213,17 +226,14 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
outFile, err := os.OpenFile(fmt.Sprintf("./%s/%s-raw.%v", dirName, st.Name(), counter), os.O_CREATE|os.O_RDWR, os.FileMode(0755))
|
fileName := fmt.Sprintf("./%s/%s-%v.png", dirName, dirName, counter)
|
||||||
if err != nil {
|
WriteToPNG(decompressedFrameData, currentPalette, fileName, imageDescriptor.Width, imageDescriptor.Height, transparencyIndex)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
outFile.Write(decompressedFrameData)
|
|
||||||
|
|
||||||
counter++
|
counter++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
currentPalette = globalPalette
|
||||||
|
|
||||||
// skip the terminator byte
|
// skip the terminator byte
|
||||||
file.Seek(1, os.SEEK_CUR)
|
file.Seek(1, os.SEEK_CUR)
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/binary"
|
||||||
|
"hash/crc32"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeHeader(pngFile *os.File) {
|
||||||
|
header := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}
|
||||||
|
_, err := pngFile.Write(header)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIHDR(pngFile *os.File, width uint16, height uint16) {
|
||||||
|
chunkLength := []byte{0x00, 0x00, 0x00, 0x0D} // always 13
|
||||||
|
IHDR := []byte{0x49, 0x48, 0x44, 0x52} // IHDR string
|
||||||
|
bitDepth := byte(0x8) // number of bits per sample or per palette index (always 8 for indexed-color)
|
||||||
|
colorType := byte(0x3) // 3 is Indexed-color which is what's used for GIFs. this also means a PLTE chunk needs to exist
|
||||||
|
compressionMethod := byte(0x0) // always 0
|
||||||
|
filterMethod := byte(0x0) // always 0
|
||||||
|
interlaceMethod := byte(0x0) // 0 or 1
|
||||||
|
hash := make([]byte, 4) // crc-32 hash of the previous 13 bytes
|
||||||
|
|
||||||
|
chunkData := make([]byte, 13)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(chunkData[0:4], uint32(width))
|
||||||
|
binary.BigEndian.PutUint32(chunkData[4:8], uint32(height))
|
||||||
|
chunkData[8] = bitDepth
|
||||||
|
chunkData[9] = colorType
|
||||||
|
chunkData[10] = compressionMethod
|
||||||
|
chunkData[11] = filterMethod
|
||||||
|
chunkData[12] = interlaceMethod
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(hash, crc32.Update(crc32.ChecksumIEEE(IHDR), crc32.IEEETable, chunkData))
|
||||||
|
|
||||||
|
_, err := pngFile.Write(chunkLength)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(IHDR)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(chunkData)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(hash)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePLTE(pngFile *os.File, palette Palette) {
|
||||||
|
chunkLength := make([]byte, 4)
|
||||||
|
PLTE := []byte{0x50, 0x4C, 0x54, 0x45}
|
||||||
|
hash := make([]byte, 4)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(chunkLength, uint32(len(palette)*3))
|
||||||
|
binary.BigEndian.PutUint32(hash, crc32.Update(crc32.ChecksumIEEE(PLTE), crc32.IEEETable, palette.MarshalBinary()))
|
||||||
|
|
||||||
|
_, err := pngFile.Write(chunkLength)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(PLTE)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(palette.MarshalBinary())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(hash)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writetRNS(pngFile *os.File, transparencyIndex int) {
|
||||||
|
chunkLength := make([]byte, 4)
|
||||||
|
tRNS := []byte{0x74, 0x52, 0x4E, 0x53}
|
||||||
|
hash := make([]byte, 4)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(chunkLength, uint32(transparencyIndex)+1)
|
||||||
|
|
||||||
|
data := make([]byte, transparencyIndex+1)
|
||||||
|
for i := 0; i < transparencyIndex; i++ {
|
||||||
|
data[i] = 0xFF
|
||||||
|
}
|
||||||
|
|
||||||
|
data[transparencyIndex] = 0
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(hash, crc32.Update(crc32.ChecksumIEEE(tRNS), crc32.IEEETable, data))
|
||||||
|
|
||||||
|
_, err := pngFile.Write(chunkLength)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(tRNS)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(hash)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialize(data []byte, width int, height int) []byte {
|
||||||
|
b := make([]byte, 0, (width+1)*height)
|
||||||
|
for i := 0; i < height; i++ {
|
||||||
|
b = append(b, 0)
|
||||||
|
b = append(b, data[width*i:width*(i+1)]...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIDAT(pngFile *os.File, data []byte, width uint16, height uint16) {
|
||||||
|
chunkLength := make([]byte, 4)
|
||||||
|
IDAT := []byte{0x49, 0x44, 0x41, 0x54}
|
||||||
|
hash := make([]byte, 4)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(chunkLength, uint32(len(data)))
|
||||||
|
binary.BigEndian.PutUint32(hash, crc32.Update(crc32.ChecksumIEEE(IDAT), crc32.IEEETable, data))
|
||||||
|
|
||||||
|
_, err := pngFile.Write(chunkLength)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(IDAT)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer, err := zlib.NewWriterLevel(&buf, zlib.BestCompression)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer writer.Close()
|
||||||
|
_, err = writer.Write(serialize(data, int(width), int(height)))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
writer.Flush()
|
||||||
|
_, err = pngFile.Write(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = pngFile.Write(hash)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIEND(pngFile *os.File) {
|
||||||
|
chunkLength := []byte{0x00, 0x00, 0x00, 0x00}
|
||||||
|
IEND := []byte{0x49, 0x45, 0x4E, 0x44}
|
||||||
|
hash := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(hash, crc32.Update(crc32.ChecksumIEEE(IEND), crc32.IEEETable, nil))
|
||||||
|
|
||||||
|
IENDChunk := append(chunkLength, append(IEND, hash...)...)
|
||||||
|
_, err := pngFile.Write(IENDChunk)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteToPNG(data []byte, palette Palette, fileName string, width uint16, height uint16, transparenyIndex int) {
|
||||||
|
pngFile, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, fs.FileMode(0755))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeHeader(pngFile)
|
||||||
|
writeIHDR(pngFile, width, height)
|
||||||
|
writePLTE(pngFile, palette)
|
||||||
|
if transparenyIndex != -1 {
|
||||||
|
writetRNS(pngFile, transparenyIndex)
|
||||||
|
}
|
||||||
|
writeIDAT(pngFile, data, width, height)
|
||||||
|
writeIEND(pngFile)
|
||||||
|
pngFile.Close()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user