snowkit / gif

Haxe GIF encoder, implementing the NeuQuant and LZW encoding algorithms
40 stars 13 forks source link

Gif pixel color off? #3

Open Justinfront opened 9 years ago

Justinfront commented 9 years ago

See 4 pixel gif here using code below: test And what it should look like. test

/*
-neko bin/test.n
#-cpp cpp
-debug
-cp src
-main Main
-cmd cd bin
-cmd neko test.n
*/

package;
import haxe.io.UInt8Array;
import gif.GifEncoder;
/*
import bitsAndBytes.HexViewer;
import sys.io.File;
import sys.io.FileInput;
import sys.io.FileOutput;
*/
import Sys;
class Main
{
        var fname = "test.gif";    
    var width = 2;
    var height = 2;
    var fin: FileInput;
    static function main(){ new Main(); } public function new(){

        trace( 'Main running, saving pixles to ' + fname );
        var encoder = new GifEncoder(-1, 100, false);
        encoder.startFile( fname );
        var data = UInt8Array.fromArray( smallGif() );
        var frame = {
            width: width,
            height: height,
            data: data
        }
        trace( 'adding frame' );
        var t1 = Sys.time();
        encoder.addFrame( frame );
        trace( Sys.time() - t1 );
        encoder.finish();
        trace('now read ');
        //readHexToTerminal( fname, HEX );

        }
/*
    public function readHexToTerminal( nom: String, mode: Mode ){
                fin = File.read( nom, true );
                var reader = new HexViewer( fin );
        reader.mode = mode;
                reader.read();
                fin.close;
        trace('close');
    }
  */  
    function smallGif():Array<Int>{
        var red = { r: 0xff, g: 0x00, b:0x00 };
        var green = { r: 0x00, g: 0x99, b:0x66 };
        var blue = { r: 0x00, g: 0x99, b: 0xcc };
        var pink = { r: 0xFF, g: 0x66, b: 0x99 };
        var arr = new Array<Int>();
        arr.push( pink.r ); arr.push( pink.g ); arr.push( pink.b );
        arr.push( green.r ); arr.push( green.g ); arr.push( green.b );
        arr.push( blue.r ); arr.push( blue.g ); arr.push( blue.b );
        arr.push( red.r ); arr.push( red.g ); arr.push( red.b );
        return arr;
    }
}

Produced hex

47 49 46 38    39 61 02 00    02 00 F7 00    00 E6 00 00    E6 00 00 E5    
00 00 E4 00    00 E2 00 00    DF 00 00 DC    00 00 D9 01    01 D5 01 01    
D0 01 01 CB    02 02 C5 02    02 BF 03 03    B8 03 03 B1    04 04 A9 05    
05 A1 06 06    98 07 07 8F    08 08 86 09    09 7C 0B 0B    71 0C 0C 66    
0E 0E 5B 10    10 4F 12 12    43 14 14 36    16 16 29 19    19 1C 1C 1C    
1D 1D 1D 1E    1E 1E 1F 1F    1F 20 20 20    21 21 21 22    22 22 23 23    
23 24 24 24    25 25 25 26    26 26 27 27    27 28 28 28    29 29 29 2A    
2A 2A 2B 2B    2B 2C 2C 2C    2D 2D 2D 2E    2E 2E 2F 2F    2F 30 30 30    
31 31 31 32    32 32 33 33    33 34 34 34    35 35 35 36    36 36 37 37    
37 38 38 38    39 39 39 3A    3A 3A 3B 3B    3B 3C 3C 3C    3D 3D 3D 3E    
3E 3E 3F 3F    3F 40 40 40    41 41 41 42    42 42 43 43    43 44 44 44    
45 45 45 46    46 46 47 47    47 48 48 48    44 4E 4A 40    53 4D 39 5D    
57 32 65 61    2C 6D 6A 26    73 72 21 79    7A 1D 7E 81    18 82 88 15    
86 8E 11 89    93 0F 8C 99    0C 8F 9E 0A    91 A2 08 92    A6 06 93 AA    
05 95 AE 04    95 B1 03 96    B4 02 97 B7    02 97 B9 01    98 BC 01 98    
BD 00 98 BF    00 98 C1 00    98 C2 00 98    C3 00 98 C4    00 98 C4 00    
98 C5 00 98    C5 00 98 C5    00 98 C4 00    98 C4 00 98    C3 00 98 C2    
01 98 C1 01    98 BF 01 98    BE 02 98 BC    03 97 BA 04    97 B8 05 97    
B5 07 96 B3    09 96 B0 0B    95 AD 0E 94    AA 11 94 A7    15 93 A4 1C    
92 A1 23 90    9E 2D 8E 9B    38 8C 99 45    8A 96 54 87    94 66 84 93    
7A 81 91 92    7C 90 AC 78    90 CA 72 8F    CF 72 91 D4    71 92 D9 70    
93 DD 6F 94    E1 6E 95 E5    6D 95 E8 6D    96 EC 6C 96    EE 6B 97 F1    
6A 97 F4 6A    98 F6 69 98    F8 68 98 F9    68 98 FB 67    98 FC 67 98    
FD 66 98 FE    66 98 FE 66    98 FE 66 98    FF 66 99 FE    66 99 FE 66    
99 FE 66 99    FD 66 99 FC    67 99 FB 68    99 FA 68 99    F9 69 99 F7    
6A 99 F6 6C    9A F4 6D 9A    F2 6E 9A F0    70 9B EE 72    9B EB 74 9C    
E9 76 9D E7    79 9D E4 7B    9E E1 7E 9F    DE 81 A0 DC    85 A2 D9 88    
A3 D6 8C A4    D3 90 A6 D0    94 A8 CC 98    AA C9 9D AC    C6 A2 AE C3    
A7 B0 BF AD    B3 BC B3 B6    B9 B9 B9 BA    BA BA BB BB    BB BC BC BC    
BD BD BD BE    BE BE BF BF    BF C0 C0 C0    C1 C1 C1 C2    C2 C2 C3 C3    
C3 C4 C4 C4    C5 C5 C5 C6    C6 C6 C7 C7    C7 C8 C8 C8    C9 C9 C9 CA    
CA CA CB CB    CB CC CC CC    CD CD CD CE    CE CE CF CF    CF D0 D0 D0    
D1 D1 D1 D2    D2 D2 D3 D3    D3 D4 D4 D4    D5 D5 D5 D6    D6 D6 D7 D7    
D7 D8 D8 D8    D9 D9 D9 DA    DA DA DB DB    DB DC DC DC    DD DD DD DE    
DE DE DF DF    DF E0 E0 E0    E1 E1 E1 E2    E2 E2 E3 E3    E3 E4 E4 E4    
E5 E5 E5 E6    E6 E6 E7 E7    E7 E8 E8 E8    E9 E9 E9 EA    EA EA EB EB    
EB EC EC EC    ED ED ED EE    EE EE EF EF    EF F0 F0 F0    F1 F1 F1 F2    
F2 F2 F3 F3    F3 F4 F4 F4    F5 F5 F5 F6    F6 F6 F7 F7    F7 F8 F8 F8    
F9 F9 F9 FA    FA FA FB FB    FB FC FC FC    FD FD FD FE    FE FE FF FF    
FF 21 F9 04    00 00 00 00    00 2C 00 00    00 00 02 00    02 00 00 08    
07 00 33 59    39 13 20 20    00 3B

By comparisom I tried encoding 4 pixels with my unfinished gif encoder. I got correct colors and more sensible filesize.

47 49 46 38    39 61 02 00    02 00 91 00    00 FF 66 99    00 99 66 00    
99 CC FF 00    00 21 F9 04    00 00 00 00    00 2C 00 00    00 00 02 00    
02 00 00 02    03 44 34 05    00 00 00 00    00 3B

Obviously the snow one is more complete I am just trying to get more understanding of the comparative workings and I am thinking maybe it would be ideal to blend some aspects to get best performance for small images, and also seems to show an issue with color.

Justinfront commented 9 years ago

Sorry the pixels are pretty small if you grab it and put it on a mac the preview will show difference one looks green top right and the other looks blue, my encoder only works well up to 90x90 ish but colors seem to be correct and filesizes smaller.

I also wanted to ask if the LZW approach you use - does it encode each color channel separately, because it seemed to... c = nextPixel() where nextPixel was return pixAry[curPixel - 1] & 0xff; which looked like it was only one color channel not a whole pixel but I was a bit lost so not sure, whereas my encoder... c = Std.string( encodedPixels[ i ] ); will return 0,1,2,3 the colors indexed from 0. Just trying to get my head round workings.

Justinfront commented 9 years ago

Just to be clear 0x009966; in any graphics app looks greenish, but in your gif it looks decidedly blue I don't know if it's an issue with UInt8Array or something else but my test code looks valid.

ruby0x1 commented 9 years ago

Quality setting of 100 is 100% compression. Try with 0~15 for the highest quality.

Justinfront commented 9 years ago

Quality does not seem to effect the result. Instead of 0x009966 the square is colored 0x0A91A2

KeyMaster- commented 9 years ago

I suspect that the problem lies in the colour quantization. I don't really know the algorithm, but this line in NeuQuant.hx suggests to me that the algorithm wants a minimum picture size of 503 pixels (though that doesn't seem to be an absolute minimum since it still kind of works for those 4-pixel test cases).

I've run a test with an image that just repeats those 4 test colours, but in an image of 504 pixels, and the colours are correct. Images with fewer pixels (500 was my test) also seem to output correct colours, so it doesn't seem like anything below the 503-pixel limit goes wrong, however it seems likely that very small images just don't work for it.

I also found the original website and paper for the quantization algorithm, I've only skimmed it so far but this may be a starting point if you want to look into this further: http://members.ozemail.com.au/~dekker/NEUQUANT.HTML

ruby0x1 commented 9 years ago

@chman may also have an opinion

azrafe7 commented 8 years ago

As @KeyMaster- suggests it seems to be a limitation of the NeuQuant algorithm (in this case probably also exacerbated by the fact that the input has only 4 colors).

In support to that idea there's for example this java port that explicitly throws an exception if (width * height) < 503 (search for "too small").

One possible fix would be to run a simpler algorithm to build the palette when the input is smaller than 256 total pixels (as they would surely map to - at most - 256 entries). It would also be faster and more accurate: the output would 100% match the original rgb values.

Well... if anyone's interested I've going down that path and came up with this:

        /** Analyzes pixels' colors and builds the color table (does no color-quantization/palette-reduction). 
         *  NOTE: naive substitute of `analyze()` (assumes unique colors to be <= 256).
         */
        function simplemapping(pixels:UInt8Array) {
            var rgb2index = new Map<Int, Int>(); // maps rgb to index
            var index2rgb = []; // reverse look-up

            // analyze pixels
            var nextIndex = 0;
            var k = 0;
            for (i in 0...(width * height))
            {
                var rgb = (pixels[k++] << 16) | (pixels[k++] << 8) | pixels[k++];
                var index = rgb2index[rgb];
                if (index == null) {
                    index = nextIndex++;
                    rgb2index[rgb] = index;
                    index2rgb[index] = rgb;
                }
                indexedPixels[i] = index;
            }

            colorTab = new UInt8Array(nextIndex * 3);

            // build color table
            k = 0;
            for (rgb in index2rgb) {
                colorTab[k++] = (rgb >> 16) & 0xFF;
                colorTab[k++] = (rgb >> 8) & 0xFF;
                colorTab[k++] = rgb & 0xFF;
            }

            // max 256 unique colors
            colorDepth = 8;
            paletteSize = 7;
        }

Going further one can prescan the image and count the unique colors, and then decide which algorithm to run based on whether it exceeds 256 or not.