syoyo / tinyexr

Tiny OpenEXR image loader/saver library
708 stars 138 forks source link

TinyEXR corrupts tiny .exr #40

Closed ghost closed 7 years ago

ghost commented 7 years ago

As I was constructing a simple example for my Haskell library I found that if compressed stream is longer then uncompressed one, the latter has to be stored. In fact, I found a reference to it in spec (http://www.openexr.com/openexrfilelayout.pdf):

Otherwise, the uncompressed pixels are fed to the appropriate compressor, and either the compressed or the uncompressed data are stored in the file, whichever is smaller.

The problem can be easily fixed by checking the length of the stream and writing the shorter one (the header still keeps the compression flag).

Here is tweaked example that writes single red pixel, but when viewed in GIMP, it is blue (with ZIPS GIMP reports corrupted file):

#include <stdio.h>
#include <stdlib.h>

#define TINYEXR_IMPLEMENTATION
#include "tinyexr.h"

int
main(void)
{
        EXRHeader header;
        InitEXRHeader(&header);

        header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;

        EXRImage image;
        InitEXRImage(&image);

        image.num_channels = 3;

        float const images[3][1] = {
                {1.0f},
                {0.0f},
                {0.0f},
        };

        float const *const image_ptr[3] = {
                images[2],
                images[1],
                images[0],
        };

        image.images = (unsigned char **)image_ptr;
        image.width = 1;
        image.height = 1;

        header.num_channels = 3;
        header.channels = (EXRChannelInfo *)malloc(sizeof(EXRChannelInfo) * header.num_channels); 
        // Must be BGR(A) order, since most of EXR viewers expect this channel order.
        strncpy(header.channels[0].name, "B", 255); header.channels[0].name[strlen("B")] = '\0';
        strncpy(header.channels[1].name, "G", 255); header.channels[1].name[strlen("G")] = '\0';
        strncpy(header.channels[2].name, "R", 255); header.channels[2].name[strlen("R")] = '\0';

        header.pixel_types = (int *)malloc(sizeof(int) * header.num_channels); 
        header.requested_pixel_types = (int *)malloc(sizeof(int) * header.num_channels);
        for (int i = 0; i < header.num_channels; i++) {
                header.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image
                header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of output image to be stored in .EXR
        }

        const char* err;
        int const ret = SaveEXRImageToFile(&image, &header, "test.exr", &err);
        if (ret != TINYEXR_SUCCESS) {
                fprintf(stderr, "Save EXR err: %s\n", err);
                return ret;
        }
}
syoyo commented 7 years ago

Thank you for reporting the issue!

I did quick fix in https://github.com/syoyo/tinyexr/tree/compress-size-check branch. (The test code is added to test/unit/tester.cc)

Please take a look at this branch and confirm it works or not.

ghost commented 7 years ago

I rerun my test and looked at the code and it seems fixed; thanks.

syoyo commented 7 years ago

Thank you for testing!

Merged into master.