Image Format Manager?

I may be too used to the audio side of JUCE, but I was expecting a class resembling the “AudioFormatManager” for images (an “ImageFormatManager”?); an object dedicated to handling the creation of an Image from a File or such, and lets the programmer/users add custom image formats. Have I overlooked something that does all of this already?

Since all the loading of Image objects from streams/data/files seems to exist within ImageFileFormat itself, I broke it apart and put it into a dedicate “manager” class (use and distribute freely, if Jules’ doesn’t implement this):

Header (Yes, I use “juce::” - search and replace will fix that if undesirable):

class ImageFormatManager /*final*/
{
public:
    /** Constructor */
    ImageFormatManager() { }

    /** Destructor */
    ~ImageFormatManager() { }

    //==============================================================================
    /** Adds a format to the manager's list of available file types.

        The object passed-in will be deleted by this object, so don't keep a pointer to it!
    */
    void registerFormat (juce::ImageFileFormat* newFormat);

    /** Handy method to make it easy to register the formats that come with JUCE.

        Currently, this will add PNG, JPEG and GIF to the list.
    */
    void registerBasicFormats();

    /** Clears the list of known formats. */
    void clearFormats();

    /** Returns the number of currently registered file formats. */
    int getNumKnownFormats() const;

    /** Returns one of the registered file formats. */
    juce::ImageFileFormat* getKnownFormat (int index) const;

    /** Iterator access to the list of known formats. */
    juce::ImageFileFormat** begin() const noexcept { return knownFormats.begin(); }

    /** Iterator access to the list of known formats. */
    juce::ImageFileFormat** end() const noexcept { return knownFormats.end(); }

    //==============================================================================
    /** Tries the built-in formats to see if it can find one to read this stream.
        There are currently built-in decoders for PNG, JPEG and GIF formats.
        The object that is returned should not be deleted by the caller.
        @see canUnderstand, decodeImage, loadFrom
    */
    juce::ImageFileFormat* findFormatForStream (juce::InputStream& input);

    /** Looks for a format that can handle the given file extension.
        There are currently built-in formats for PNG, JPEG and GIF formats.
        The object that is returned should not be deleted by the caller.
    */
    juce::ImageFileFormat* findFormatForFile (const juce::File& file);

    //==============================================================================
    /** Tries to load an image from a stream.

        This will use the findImageFormatForStream() method to locate a suitable
        codec, and use that to load the image.

        @returns The image that was decoded, or an invalid image if it fails.
    */
    juce::Image loadFrom (juce::InputStream& input);

    /** Tries to load an image from a file.

        This will use the findImageFormatForStream() method to locate a suitable
        codec, and use that to load the image.

        @returns The image that was decoded, or an invalid image if it fails.
    */
    juce::Image loadFrom (const juce::File& file);

    /** Tries to load an image from a block of raw image data.

        This will use the findImageFormatForStream() method to locate a suitable
        codec, and use that to load the image.

        @returns  The image that was decoded, or an invalid image if it fails.
    */
    juce::Image loadFrom (const void* rawData, size_t numBytesOfData);

private:
    //==============================================================================
    juce::OwnedArray<juce::ImageFileFormat> knownFormats;

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageFormatManager)
};

Implementation:

//==============================================================================
void ImageFormatManager::registerFormat (juce::ImageFileFormat* newFormat)
{
    knownFormats.addIfNotAlreadyThere (newFormat);
}

void ImageFormatManager::registerBasicFormats()
{
    knownFormats.add (new juce::JPEGImageFormat());
    knownFormats.add (new juce::GIFImageFormat());
    knownFormats.add (new juce::PNGImageFormat());
}

void ImageFormatManager::clearFormats()
{
    knownFormats.clear();
}

int ImageFormatManager::getNumKnownFormats() const
{
    return knownFormats.size();
}

juce::ImageFileFormat* ImageFormatManager::getKnownFormat (const int index) const
{
    return knownFormats[index];
}

//==============================================================================
juce::ImageFileFormat* ImageFormatManager::findFormatForStream (juce::InputStream& input)
{
    const juce::int64 streamPos = input.getPosition();

    for (int i = knownFormats.size(); --i >= 0;)
    {
        if (knownFormats.getUnchecked (i)->canUnderstand (input))
        {
            input.setPosition (streamPos);
            return knownFormats.getUnchecked (i);
        }
    }

    return nullptr;
}

juce::ImageFileFormat* ImageFormatManager::findFormatForFile (const juce::File& file)
{
    for (int i = knownFormats.size(); --i >= 0;)
        if (knownFormats.getUnchecked (i)->usesFileExtension (file))
            return knownFormats.getUnchecked (i);

    return nullptr;
}

//==============================================================================
juce::Image ImageFormatManager::loadFrom (juce::InputStream& input)
{
    if (juce::ImageFileFormat* const format = findFormatForStream (input))
        return format->decodeImage (input);

    return juce::Image::null;
}

juce::Image ImageFormatManager::loadFrom (const juce::File& file)
{
    juce::FileInputStream stream (file);

    if (stream.openedOk())
    {
        juce::BufferedInputStream b (stream, 8192); //Why 8192?
        return loadFrom (b);
    }

    return juce::Image::null;
}

juce::Image ImageFormatManager::loadFrom (const void* rawData, const size_t numBytes)
{
    if (rawData != nullptr && numBytes > 4) //Why 4 specifically, and not something like 0?
    {
        juce::MemoryInputStream stream (rawData, numBytes, false);
        return loadFrom (stream);
    }

    return juce::Image::null;
}

Mind taking a look at this Jules?

Well, there is the ImageCache class, which combines this sort of behaviour with a cache - though it doesn’t appear to have an interface for registering custom types.

TBH the main reason I’ve never done that is because nobody’s ever asked for custom types - I guess it’s a lot less commonly needed for images than for audio. Are you suggesting this because you actually need to add a new format, or just because it’d be nice to make it symmetrical with the audio classes?

A new format, of course - JUCE doesn’t support BMP and TGA files, afterall. Plus, there’s nothing stopping people from writing custom formats, just like audio.

Edit: I’m just looking for a tad more flexibility in the image department - I can’t be the only one…

I’ve had to roll my own class to read additional image formats, so this would be useful for me as well.

Thanks,

Rail

Yes, I’m not arguing with the principle, and that class is rather old and needs updating.

…but the only thing that makes me hesitate is that if I refactor the format manager class, then I’d stop it from being a singleton (like the audio format manager), and that’d be awkward for the ImageCache class, which would then need to be given the format manager that it should use in its calls, so all the code that uses ImageCache would need re-writing. Not a huge deal, but would be an annoyance for people.

Please bear in mind pixel formats as anything gets tweaked. Some image importers should be able to support decoding to multiple pixel formats - most formats have at least native and RGB, and some formats support 16-bit RGB too.

Bruce

Indeed… but it could be possible to wrap an instance of the format manager in the singleton, and expose adding formats, and the common data/stream/file-to-Image methods?

Of course I could do that, but if I refactor this class, I’d want to take the opportunity to remove any singletons, not to add more of them!

Fair enough - was just thinking out loud!