Question

I'm using Notepad++ to do most of my coding currently. However, I'd like to change its icons, just for customization sake really.

So what I did was to open the .exe with Resource Hacker, and see where the icons were. They seem to be in bitmap format, which is weird because they have transparency, and replacing them with any "Bitmap" (BM header, "Windows Bitmap") format I know of doesn't work (the icon isn't shown).

So I'd like to ask, which format are these files in, and how can I produce them?

Sample of the bitmaps can be found in the Notepad++ repository:
newFile http://sourceforge.net/p/notepad-plus/code/735/tree/trunk/PowerEditor/src/icons/newFile.bmp?format=raw openFile http://sourceforge.net/p/notepad-plus/code/735/tree/trunk/PowerEditor/src/icons/openFile.bmp?format=raw saveFile http://sourceforge.net/p/notepad-plus/code/735/tree/trunk/PowerEditor/src/icons/saveFile.bmp?format=raw saveAll http://sourceforge.net/p/notepad-plus/code/735/tree/trunk/PowerEditor/src/icons/saveAll.bmp?format=raw closeFile http://sourceforge.net/p/notepad-plus/code/735/tree/trunk/PowerEditor/src/icons/closeFile.bmp?format=raw closeAll http://sourceforge.net/p/notepad-plus/code/735/tree/trunk/PowerEditor/src/icons/closeAll.bmp?format=raw

As you can see, they have a grey matte, which makes me think the alpha channel is stored in the format in some non-standard way. (or, it has binary transparency, and #C0C0C0 means transparent).

It is also fairly large, having more than 5 bytes per each pixel represented (!).

This is how the header of the first bitmap above looks:

Hex Workshop

Any idea? Just knowing what format this is would be enough.

Was it helpful?

Solution

This is an 8BPP (8 bits per pixel) indexed bitmap. After the "BM" there is a 52-byte header, a palette of 256 colours, and the indexed pixel data. Each colour is 4 bytes, in BGR_ format (the fourth byte isn't used; if it was an alpha channel, you would see FF instead of 00).

The indexed pixel data starts at 0x436. It's simple: one byte represents one index in the palette. For example, the first few bytes are 07 07 18 18. The 0x07 will make that pixel use the 8th colour in the palette, which is #C0C0C0; and the 0x18 will make that pixel use the 25th colour in the palette, which is #CECECE.

I'm guessing that transparency is handled as you guessed. I see 07 wherever a transparent pixel would be, and the corresponding colour in the palette is #C0C0C0.

Note that the pixel data is stored upside-down. That is, the first 16 bytes at offset 0x436 represent the bottom row of pixels.

It's explained in more detail here: http://en.wikipedia.org/wiki/BMP_file_format#File_structure

EDIT: As for how to produce them, just ask your favourite image editing software to export the image as a 256-colour bitmap (or an 8BPP bitmap, whichever is available). Note that Microsoft Paint will mangle the colours if you save it after drawing it, so save it before you actually draw anything.

OTHER TIPS

All that Jeff pointed out is correct and will keep being the accepted answer. However, in case somebody else might want to tweak NP++, I've made a small converter you can use to convert any 8-bit indexed bitmap into one you can use with NP++ (through resource hacker or similar).

It is coded in Javascript, tested only with Chrome, and the code is terribly sloppy: JsFiddle

To use it, create an 8-bit 16x16 indexed bitmap in your favorite program, and drag-drop it into that thing. It will output some debug details and give you a link so you can "download" a compatible bitmap (but all of this happens client-side with HTML5).

Edit: SO complains that I put a link to JsFiddle without posting relevant code here, so well, I'll post it. Here it is, completely self-contained HTML:

<!DOCTYPE html>
<html>
    <head>
        <title>Endoblast</title>
        <style>
            html { background: #F8F8F8; }
            #drop-zone {
                position: fixed;
                left: 0;
                right: 0;
                bottom: 0;
                top: 0;
                border: 4px dashed #DDD;
                z-index: -1;
            }
            #result { font: 12px Consolas; padding-bottom: 240px; }
            .color { margin-right: -7px; position: relative; cursor: default; }
            .color > .top, .color > .bottom { position: absolute; left: 0; right: 0; height: 2px; }
            .color > .top    { position: absolute; top:    0; background: rgba(255, 255, 255, 0.2); }
            .color > .bottom { position: absolute; bottom: 0; background: rgba(  0,   0,   0, 0.05); }
        </style>
    </head>
    <body>
        <div id="drop-zone"></div>
        <p id="result"></p>
        <script>

            if (window.File && window.FileReader && window.FileList && window.Blob) {

                function Readr(bytes) {

                    var data = bytes;

                    var offset = 0;

                    this.nextStr = function(amount) {

                        var result = []

                        while (amount--) result.push(data[offset++]);

                        return toStr(result);

                    }

                    this.nextInt = function(amount) {

                        if (amount == 1 || !amount) return data[offset++];

                        if (amount == 2) return toInt16(data[offset++], data[offset++]);

                        if (amount == 4) return toInt32(data[offset++], data[offset++], data[offset++], data[offset++]);

                        return next(amount);

                    }

                    var next = this.next = function(amount) {

                        var result = []

                        while (amount--) result.push(data[offset++]);

                        return result;

                    }

                }

                function Color(r, g, b) {

                    this.r = r;
                    this.g = g;
                    this.b = b;

                }

                function toStr(bytes) {
                    var result = '';
                    for (var i = 0; i < bytes.length; i++) result += String.fromCharCode(bytes[i]);
                    return result;
                }

                function toInt16(b1, b2) { return (b1 << 0) + (b2 << 8); }

                function toInt32(b1, b2, b3, b4) { return (b1 << 0) + (b2 << 8) + (b3 << 16) + (b4 << 24); }

                function fromInt16(int16) {

                    return [
                         int16 & parseInt('00FF', 16),
                        (int16 & parseInt('FF00', 16)) >> 8
                    ];

                }

                function fromInt32(int32) {

                    return [
                         int32 & parseInt('000000FF', 16),
                        (int32 & parseInt('0000FF00', 16)) >> 8,
                        (int32 & parseInt('00FF0000', 16)) >> 16,
                        (int32 & parseInt('FF000000', 16)) >> 24
                    ];

                }

                function log(text) { document.getElementById('result').innerHTML += text + '\n<br/>'; }

                function logHtml(html) { document.getElementById('result').innerHTML += html + '<br/>'; }

                var bitCounts = {
                    '1': '1-bit',
                    '2': '2-bit',
                    '4': '4-bit',
                    '8': '8-bit',
                    '16': '16-bit',
                    '24': '24-bit',
                    '32': '32-bit'
                };

                var compressions = {
                    '0': 'RGB (uncompressed)'
                };

                function handleFileSelect(evt) {

                    evt.stopPropagation();
                    evt.preventDefault();

                    var files = evt.dataTransfer.files; // FileList object.

                    var reader = new FileReader();

                    reader.onload = function (event) {

                        var b = [].slice.call(new Uint8Array(event.target.result));

                        var r = new Readr(b);

                        if (r.nextStr(2) == 'BM')
                            log('BM header found.');
                        else
                            return log('File is not a BMP.');

                        var fileSize = r.nextInt(4);

                        log('File size: ' + fileSize + ' Bytes.');

                        var reserved = r.next(4)

                        var offset = r.nextInt(4);

                        log('Image data offset: ' + offset + ' Bytes.');
                        log('Image data size: ' + (fileSize - offset) + ' Bytes.');

                        var headerSize = r.nextInt(4);

                        log('DIB header size: ' + headerSize + ' Bytes.');

                        var header = r.next(headerSize - 4);

                        var hr = new Readr(header);

                        var width = hr.nextInt(4);
                        var height = hr.nextInt(4);

                        log('Image width: ' + width + 'px.');
                        log('Image height: ' + height + 'px.');

                        var planes = hr.nextInt(2);

                        log('Color planes: ' + planes + '.');

                        var bitCount = bitCounts[hr.nextInt(2)] || 'unknown';
                        var compression = compressions[hr.nextInt(4)] || 'unknown';

                        log('Bit depth: ' + bitCount + '.');
                        log('Compression: ' + compression + '.');

                        var pixelCount = hr.nextInt(4);

                        log('Pixel count: ' + pixelCount + '.');

                        var yPPM = hr.nextInt(4);
                        var xPPM = hr.nextInt(4);

                        var colorTableSize = hr.nextInt(4);

                        log('Color table size: ' + colorTableSize + '.');

                        var importantColors = hr.nextInt(4);

                        log('Important colors in color table: ' + importantColors + '.');

                        var colorTable = r.next(colorTableSize * 4);

                        var cr = new Readr(colorTable);

                        var colors = [];

                        for (var i = 0; i < colorTableSize; i++) {

                            var B = cr.nextInt(),
                                G = cr.nextInt(),
                                R = cr.nextInt(),
                                _ = cr.nextInt();

                            colors.push(new Color(R, G, B));

                        }

                        function createColorCell(r, g, b, desc) {

                            var title = 'title="' + desc + ': rgb(' + r + ', ' + g + ', '  + b + ')"'

                            return '<span class="color" ' + title + ' style="background:rgb(' + r + ',' + g + ',' + b + ')">'
                                 + '<span class="top"></span><span class="bottom"></span>&nbsp;&nbsp;</span> ';

                        }

                        var cells = '';

                        for (var i = 0; i < colors.length; i++) {

                            var c = colors[i];

                            cells += createColorCell(c.r, c.g, c.b, 'Color at index ' + i + ' in the color table');

                        }

                        if (colorTableSize) logHtml('Colors in the color table: ' + cells); else return log('No color table.');

                        log('Bitmap data, mapped to the color table is shown below.');

                        var bitmapCells = '';

                        var bitmapRows = [];

                        var indexes = [];

                        for (var y = height; y > 0; y--) {

                            for (var x = 0; x < width; x++) {

                                var index = r.nextInt();

                                indexes.push(index);

                                var color = colors[index];

                                var desc = 'Pixel x' + x + ', y' + y + ', mapped to index ' + index + ' in the color table, '
                                         + 'which has colors rgb(' + color.r + ', ' + color.g + ', ' + color.g + ')';

                                bitmapCells += createColorCell(color.r, color.g, color.b, desc);

                            }

                            bitmapRows.push(bitmapCells);
                            bitmapCells = '';

                        }

                        for (var row = height - 1; row > -1; row--) {

                            logHtml(bitmapRows[row]);

                        }

                        var fixedData = new ArrayBuffer(1334);

                        var fixed = new Uint8Array(fixedData);

                        var fixedOffset = 0;

                        function s8(b) { fixed[fixedOffset++] = b; }

                        function s16(i16) {
                            var bytes = fromInt16(i16);
                            s8(bytes[0]);
                            s8(bytes[1]);
                        }

                        function s32(i32) {
                            var bytes = fromInt32(i32);
                            s8(bytes[0]);
                            s8(bytes[1]);
                            s8(bytes[2]);
                            s8(bytes[3]);
                        }

                        // Now we build a fixed up bitmap.

                        s16(19778); // header.
                        s32(1334); // file size.
                        s32(0); // reserved.
                        s32(1078); // DIB data offset.
                        s32(40); // DIB header size.
                        s32(16); // width.
                        s32(16); // height.
                        s16(1); // planes.
                        s16(8); // bit depth.
                        s32(0); // compression.
                        s32(256); // image size.
                        s32(0); // Xpels.
                        s32(0); // Ypels.
                        s32(256); // Colors used.
                        s32(256); // Important colors.

                        for (var i = 0; i < 1024; i++) {

                            var c = colorTable[i];

                            if (typeof c !== 'undefined')
                                s8(colorTable[i])
                            else
                                s8(((i % 4) == 3) ? 0 : 255);

                        }

                        for (var i = 0; i < 256; i++) s8(indexes[i]);

                        window.URL = window.webkitURL || window.URL;
                        window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
                        var file = new window.BlobBuilder();

                        file.append(fixed);

                        var a = document.createElement('a');
                        a.href = window.URL.createObjectURL(file.getBlob('image/bmp'));
                        a.download = 'fixed.bmp';
                        a.textContent = 'Download Notepad++ compatible bitmap';
                        document.getElementById('result').appendChild(a);

                    };

                    reader.readAsArrayBuffer(files[0]);

                }

                function handleDragOver(evt) {
                    evt.stopPropagation();
                    evt.preventDefault();
                    evt.dataTransfer.dropEffect = 'copy';
                }

                var dropZone = document.getElementById('drop-zone');
                dropZone.addEventListener('dragover', handleDragOver, false);
                dropZone.addEventListener('drop', handleFileSelect, false);

            } else {

                alert('The File APIs are not fully supported in this browser.');

            }

        </script>
    </body>
</html>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top