Reading Jpeg Metadata

I’m building a little app to view photos, and have discovered that jpegs can have incorrect rotation when being read as a Juce image.

There is a flag in JPEG exif data called ‘Orientation’. It’s a way to rotate an image without changing the actual pixel data. It doesn’t look like JUCE reads in this data, so some images taken on cameras will be rotated the wrong way. I am loading image like this:

image = JPEGImageFormat::loadFrom(file);

    auto properties = image.getProperties();
    for (int i = 0; i < properties->size(); ++i) {
        auto value = properties->getValueAt(i);
        auto name = properties->getName(i);
        DBG("name: " + String(name.toString())+" value: " + String(value.toString()));
    }

The only property that I can find is originalImageHadAlpha = 0. The exif doesn’t seem to be loaded in at all.

Googling around, it looks like someone made an exif reader for JUCE back in 2007(!) but the image class has moved on a lot since then, so it wouldn’t be trivial to try to convert it.
https://forum.juce.com/t/image-metadata-support/2086

Has anyone got a simple solution for getting the exif? I’m specifically really only looking for that one flag, although I guess having all the exif would be nice.

Any ideas appreciated. Many thanks!

I have an updated version of the exif metadata code here in juce module format: https://github.com/FigBug/Gin/blob/master/modules/gin_metadata/metadata/gin_imagemetadata.h

2 Likes

Oh wow, thanks so much this is super useful :slight_smile: Looks like some other cool functions for image contrast etc might be helpful too!

I’ve loaded the exif no problem, just wondering if there is a way to rotate images on load or do I need to read the exif and do this myself? Thanks!

You’ll need to do it yourself. If you write a nice function, I’ll accept a pull request. :grin:

Not sure how nice it will be but I’ll send you one…

Ok… So I have a solution, but I’m not sure where it would logically sit in your code so I’ll just paste it here.

To get the rotation of a JPEG in degrees:

// load exif and get rotation for photo 
int rotation = 0;
FileInputStream inputStream (file);
if (inputStream.openedOk()) {

    juce::OwnedArray<gin::ImageMetadata> metadata;
    if (gin::ImageMetadata::getFromImage(inputStream, metadata)) {

        for (int i = 0; i < metadata.size(); ++i) {
            auto pair = metadata.getUnchecked(i);
            if (pair->getType() == "EXIF") {

                auto data = pair->getAllMetadata();
                auto orientation = data.getValue("Orientation", "-1");

                if (orientation == "6") {
                    rotation = 90;
                } else if (orientation == "3") {
                    rotation = 180;
                } else if (orientation == "8") {
                    rotation = 270;
                } 
                break;
            }
        }
    }
}

I then tried two methods of rotating the photo. The first is to actually change the image data. I do this to my thumbnails before saving them. The trouble is, this is SLOW. It takes 3 seconds in debug mode to rotate a decent sized DLSR jpeg.

void rotateImage(Image &image, int degrees){

    auto start = Time::getMillisecondCounter();
    if(degrees == 90){
        auto rotated = Image(Image::PixelFormat::RGB, image.getHeight(), image.getWidth(), true);
        for (int y = 0; y < rotated.getHeight(); ++y) {
            for (int x = 0; x < rotated.getWidth(); ++x) {
                rotated.setPixelAt(rotated.getWidth() - x,y,image.getPixelAt(y,x));
            }
        }
        image = rotated;
    }
    else if(degrees == 180){
        auto rotated = Image(Image::PixelFormat::RGB, image.getWidth(), image.getHeight(), true);

        for (int x = 0; x < rotated.getWidth(); ++x) {
            for (int y = 0; y < rotated.getHeight(); ++y) {
                rotated.setPixelAt( rotated.getWidth() - x,rotated.getHeight() - y,image.getPixelAt(x,y));
            }
        }
        image = rotated;
    }
    else if(degrees == 270){
        auto rotated = Image(Image::PixelFormat::RGB, image.getHeight(), image.getWidth(), true);

        for (int x = 0; x < rotated.getWidth(); ++x) {
            for (int y = 0; y < rotated.getHeight(); ++y) {
                rotated.setPixelAt( x,rotated.getHeight() - y,image.getPixelAt(y,x));
            }
        }
        image = rotated;
    }
    else{
        jassertfalse;
    }
    DBG("ImageFunctions::rotateImage() "+ String(degrees) +" "+ String(Time::getMillisecondCounter()- start)+" ms");
}

I think doing the rotation work using Image::BitmapData would be faster, but researching this, I found a better way to do it, which is to rotate the image when drawing it in the paint method, which seems to me to be exactly what the flag was designed for.

    auto rotate = AffineTransform::rotation(2 * M_PI * (rotation / 360.0), image.getWidth() / 2.0,image.getHeight() / 0.2);
    g.drawImageTransformed(image, rotate);

This is much faster than my original approach. Hope it’s useful to someone.

The way I would approach that would be to draw the image transformed on the new image:

auto transform = juce::AffineTransform::rotation (angle, image.getWidth() * 0.5f, image.getHeight() * 0.5f);
auto newBounds = image.getBounds().transformedBy (transform);
juce::Image target (image.getFormat(), newBounds.getWidth(), newBounds.getHeight(), true);
juce::Graphics g (target);
g.drawImageTransformed (image, transform);

voila, target contains the rotated image.

You can either use multiples of juce::MathConstants<float>::pi or juce::degreesToRadians() for the angle.

This is great. I didn’t know you could use the Graphics class to create an image, I’ve never used it beyond Component::paint :slight_smile:

FYI the speed increase over my looping method was 20x.