/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2022 - Raw Material Software Limited

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 7 End-User License
   Agreement and JUCE Privacy Policy.

   End User License Agreement: www.juce.com/juce-7-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

class ImageType;
class ImagePixelData;


//==============================================================================
/**
    Holds a fixed-size bitmap.

    The image is stored in either 24-bit RGB or 32-bit premultiplied-ARGB format.

    To draw into an image, create a Graphics object for it.
    e.g. @code

    // create a transparent 500x500 image..
    Image myImage (Image::RGB, 500, 500, true);

    Graphics g (myImage);
    g.setColour (Colours::red);
    g.fillEllipse (20, 20, 300, 200);  // draws a red ellipse in our image.
    @endcode

    Other useful ways to create an image are with the ImageCache class, or the
    ImageFileFormat, which provides a way to load common image files.

    @see Graphics, ImageFileFormat, ImageCache, ImageConvolutionKernel

    @tags{Graphics}
*/
class JUCE_API  Image  final
{
public:
    //==============================================================================
    /**
    */
    enum PixelFormat
    {
        UnknownFormat,
        RGB,                /**<< each pixel is a 3-byte packed RGB colour value. For byte order, see the PixelRGB class. */
        ARGB,               /**<< each pixel is a 4-byte ARGB premultiplied colour value. For byte order, see the PixelARGB class. */
        SingleChannel       /**<< each pixel is a 1-byte alpha channel value. */
    };

    //==============================================================================
    /** Creates a null image. */
    Image() noexcept;

    /** Creates an image with a specified size and format.

        The image's internal type will be of the NativeImageType class - to specify a
        different type, use the other constructor, which takes an ImageType to use.

        @param format           the preferred pixel format. Note that this is only a *hint* which
                                is passed to the ImageType class - different ImageTypes may not support
                                all formats, so may substitute e.g. ARGB for RGB.
        @param imageWidth       the desired width of the image, in pixels - this value must be
                                greater than zero (otherwise a width of 1 will be used)
        @param imageHeight      the desired width of the image, in pixels - this value must be
                                greater than zero (otherwise a height of 1 will be used)
        @param clearImage       if true, the image will initially be cleared to black (if it's RGB)
                                or transparent black (if it's ARGB). If false, the image may contain
                                junk initially, so you need to make sure you overwrite it thoroughly.
    */
    Image (PixelFormat format, int imageWidth, int imageHeight, bool clearImage);

    /** Creates an image with a specified size and format.

        @param format           the preferred pixel format. Note that this is only a *hint* which
                                is passed to the ImageType class - different ImageTypes may not support
                                all formats, so may substitute e.g. ARGB for RGB.
        @param imageWidth       the desired width of the image, in pixels - this value must be
                                greater than zero (otherwise a width of 1 will be used)
        @param imageHeight      the desired width of the image, in pixels - this value must be
                                greater than zero (otherwise a height of 1 will be used)
        @param clearImage       if true, the image will initially be cleared to black (if it's RGB)
                                or transparent black (if it's ARGB). If false, the image may contain
                                junk initially, so you need to make sure you overwrite it thoroughly.
        @param type             the type of image - this lets you specify the internal format that will
                                be used to allocate and manage the image data.
    */
    Image (PixelFormat format, int imageWidth, int imageHeight, bool clearImage, const ImageType& type);

    /** Creates a shared reference to another image.

        This won't create a duplicate of the image - when Image objects are copied, they simply
        point to the same shared image data. To make sure that an Image object has its own unique,
        unshared internal data, call duplicateIfShared().
    */
    Image (const Image&) noexcept;

    /** Makes this image refer to the same underlying image as another object.

        This won't create a duplicate of the image - when Image objects are copied, they simply
        point to the same shared image data. To make sure that an Image object has its own unique,
        unshared internal data, call duplicateIfShared().
    */
    Image& operator= (const Image&);

    /** Move constructor */
    Image (Image&&) noexcept;

    /** Move assignment operator */
    Image& operator= (Image&&) noexcept;

    /** Destructor. */
    ~Image();

    /** Returns true if the two images are referring to the same internal, shared image. */
    bool operator== (const Image& other) const noexcept     { return image == other.image; }

    /** Returns true if the two images are not referring to the same internal, shared image. */
    bool operator!= (const Image& other) const noexcept     { return image != other.image; }

    /** Returns true if this image isn't null.
        If you create an Image with the default constructor, it has no size or content, and is null
        until you reassign it to an Image which contains some actual data.
        The isNull() method is the opposite of isValid().
        @see isNull
    */
    inline bool isValid() const noexcept                    { return image != nullptr; }

    /** Returns true if this image is not valid.
        If you create an Image with the default constructor, it has no size or content, and is null
        until you reassign it to an Image which contains some actual data.
        The isNull() method is the opposite of isValid().
        @see isValid
    */
    inline bool isNull() const noexcept                     { return image == nullptr; }

    //==============================================================================
    /** Returns the image's width (in pixels). */
    int getWidth() const noexcept;

    /** Returns the image's height (in pixels). */
    int getHeight() const noexcept;

    /** Returns a rectangle with the same size as this image.
        The rectangle's origin is always (0, 0).
    */
    Rectangle<int> getBounds() const noexcept;

    /** Returns the image's pixel format. */
    PixelFormat getFormat() const noexcept;

    /** True if the image's format is ARGB. */
    bool isARGB() const noexcept;

    /** True if the image's format is RGB. */
    bool isRGB() const noexcept;

    /** True if the image's format is a single-channel alpha map. */
    bool isSingleChannel() const noexcept;

    /** True if the image contains an alpha-channel. */
    bool hasAlphaChannel() const noexcept;

    //==============================================================================
    /** Clears a section of the image with a given colour.

        This won't do any alpha-blending - it just sets all pixels in the image to
        the given colour (which may be non-opaque if the image has an alpha channel).
    */
    void clear (const Rectangle<int>& area, Colour colourToClearTo = Colour (0x00000000));

    /** Returns a rescaled version of this image.

        A new image is returned which is a copy of this one, rescaled to the given size.

        Note that if the new size is identical to the existing image, this will just return
        a reference to the original image, and won't actually create a duplicate.
    */
    Image rescaled (int newWidth, int newHeight,
                    Graphics::ResamplingQuality quality = Graphics::mediumResamplingQuality) const;

    /** Creates a copy of this image.
        Note that it's usually more efficient to use duplicateIfShared(), because it may not be necessary
        to copy an image if nothing else is using it.
        @see getReferenceCount
    */
    Image createCopy() const;

    /** Returns a version of this image with a different image format.

        A new image is returned which has been converted to the specified format.

        Note that if the new format is no different to the current one, this will just return
        a reference to the original image, and won't actually create a copy.
    */
    Image convertedToFormat (PixelFormat newFormat) const;

    /** Makes sure that no other Image objects share the same underlying data as this one.

        If no other Image objects refer to the same shared data as this one, this method has no
        effect. But if there are other references to the data, this will create a new copy of
        the data internally.

        Call this if you want to draw onto the image, but want to make sure that this doesn't
        affect any other code that may be sharing the same data.

        @see getReferenceCount
    */
    void duplicateIfShared();

    /** Returns an image which refers to a subsection of this image.

        This will not make a copy of the original - the new image will keep a reference to it, so that
        if the original image is changed, the contents of the subsection will also change. Likewise if you
        draw into the subimage, you'll also be drawing onto that area of the original image. Note that if
        you use operator= to make the original Image object refer to something else, the subsection image
        won't pick up this change, it'll remain pointing at the original.

        The area passed-in will be clipped to the bounds of this image, so this may return a smaller
        image than the area you asked for, or even a null image if the area was out-of-bounds.
    */
    Image getClippedImage (const Rectangle<int>& area) const;

    //==============================================================================
    /** Returns the colour of one of the pixels in the image.

        If the coordinates given are beyond the image's boundaries, this will
        return Colours::transparentBlack.

        @see setPixelAt, Image::BitmapData::getPixelColour
    */
    Colour getPixelAt (int x, int y) const;

    /** Sets the colour of one of the image's pixels.

        If the coordinates are beyond the image's boundaries, then nothing will happen.

        Note that this won't do any alpha-blending, it'll just replace the existing pixel
        with the given one. The colour's opacity will be ignored if this image doesn't have
        an alpha-channel.

        @see getPixelAt, Image::BitmapData::setPixelColour
    */
    void setPixelAt (int x, int y, Colour colour);

    /** Changes the opacity of a pixel.

        This only has an effect if the image has an alpha channel and if the
        given coordinates are inside the image's boundary.

        The multiplier must be in the range 0 to 1.0, and the current alpha
        at the given coordinates will be multiplied by this value.

        @see setPixelAt
    */
    void multiplyAlphaAt (int x, int y, float multiplier);

    /** Changes the overall opacity of the image.

        This will multiply the alpha value of each pixel in the image by the given
        amount (limiting the resulting alpha values between 0 and 255). This allows
        you to make an image more or less transparent.

        If the image doesn't have an alpha channel, this won't have any effect.
    */
    void multiplyAllAlphas (float amountToMultiplyBy);

    /** Changes all the colours to be shades of grey, based on their current luminosity.
    */
    void desaturate();

    //==============================================================================
    /** Retrieves a section of an image as raw pixel data, so it can be read or written to.

        You should only use this class as a last resort - messing about with the internals of
        an image is only recommended for people who really know what they're doing!

        A BitmapData object should be used as a temporary, stack-based object. Don't keep one
        hanging around while the image is being used elsewhere.

        Depending on the way the image class is implemented, this may create a temporary buffer
        which is copied back to the image when the object is deleted, or it may just get a pointer
        directly into the image's raw data.

        You can use the stride and data values in this class directly, but don't alter them!
        The actual format of the pixel data depends on the image's format - see Image::getFormat(),
        and the PixelRGB, PixelARGB and PixelAlpha classes for more info.
    */
    class JUCE_API  BitmapData  final
    {
    public:
        enum ReadWriteMode
        {
            readOnly,
            writeOnly,
            readWrite
        };

        BitmapData (Image& image, int x, int y, int w, int h, ReadWriteMode mode);
        BitmapData (const Image& image, int x, int y, int w, int h);
        BitmapData (const Image& image, ReadWriteMode mode);
        ~BitmapData();

        /** Returns a pointer to the start of a line in the image.
            The coordinate you provide here isn't checked, so it's the caller's responsibility to make
            sure it's not out-of-range.
        */
        inline uint8* getLinePointer (int y) const noexcept                 { return data + (size_t) y * (size_t) lineStride; }

        /** Returns a pointer to a pixel in the image.
            The coordinates you give here are not checked, so it's the caller's responsibility to make sure they're
            not out-of-range.
        */
        inline uint8* getPixelPointer (int x, int y) const noexcept         { return data + (size_t) y * (size_t) lineStride + (size_t) x * (size_t) pixelStride; }

        /** Returns the colour of a given pixel.
            For performance reasons, this won't do any bounds-checking on the coordinates, so it's the caller's
            responsibility to make sure they're within the image's size.
        */
        Colour getPixelColour (int x, int y) const noexcept;

        /** Sets the colour of a given pixel.
            For performance reasons, this won't do any bounds-checking on the coordinates, so it's the caller's
            responsibility to make sure they're within the image's size.
        */
        void setPixelColour (int x, int y, Colour colour) const noexcept;

        /** Returns the size of the bitmap. */
        Rectangle<int> getBounds() const noexcept                           { return Rectangle<int> (width, height); }

        uint8* data;             /**< The raw pixel data, packed according to the image's pixel format. */
        size_t size;             /**< The number of valid/allocated bytes after data. May be smaller than "lineStride * height" if this is a section of a larger image. */
        PixelFormat pixelFormat; /**< The format of the data. */
        int lineStride;          /**< The number of bytes between each line. */
        int pixelStride;         /**< The number of bytes between each pixel. */
        int width, height;

        //==============================================================================
        /** Used internally by custom image types to manage pixel data lifetime. */
        class BitmapDataReleaser
        {
        protected:
            BitmapDataReleaser() = default;
        public:
            virtual ~BitmapDataReleaser() = default;
        };

        std::unique_ptr<BitmapDataReleaser> dataReleaser;

    private:
        JUCE_DECLARE_NON_COPYABLE (BitmapData)
    };

    //==============================================================================
    /** Copies a section of the image to somewhere else within itself. */
    void moveImageSection (int destX, int destY,
                           int sourceX, int sourceY,
                           int width, int height);

    /** Creates a RectangleList containing rectangles for all non-transparent pixels
        of the image.

        @param result           the list that will have the area added to it
        @param alphaThreshold   for a semi-transparent image, any pixels whose alpha is
                                above this level will be considered opaque
    */
    void createSolidAreaMask (RectangleList<int>& result, float alphaThreshold) const;

    //==============================================================================
    /** Returns a NamedValueSet that is attached to the image and which can be used for
        associating custom values with it.

        If this is a null image, this will return a null pointer.
    */
    NamedValueSet* getProperties() const;

    //==============================================================================
    /** Creates a context suitable for drawing onto this image.
        Don't call this method directly! It's used internally by the Graphics class.
    */
    std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() const;

    /** Returns the number of Image objects which are currently referring to the same internal
        shared image data.

        @see duplicateIfShared
    */
    int getReferenceCount() const noexcept;

    //==============================================================================
    /** @internal */
    ImagePixelData* getPixelData() const noexcept       { return image.get(); }

    /** @internal */
    explicit Image (ReferenceCountedObjectPtr<ImagePixelData>) noexcept;

    //==============================================================================
   #if JUCE_ALLOW_STATIC_NULL_VARIABLES && ! defined (DOXYGEN)
    /* A null Image object that can be used when you need to return an invalid image. */
    [[deprecated ("If you need a default-constructed var, just use Image() or {}.")]]
    static const Image null;
   #endif

private:
    //==============================================================================
    ReferenceCountedObjectPtr<ImagePixelData> image;

    JUCE_LEAK_DETECTOR (Image)
};


//==============================================================================
/**
    This is a base class for holding image data in implementation-specific ways.

    You may never need to use this class directly - it's used internally
    by the Image class to store the actual image data. To access pixel data directly,
    you should use Image::BitmapData rather than this class.

    ImagePixelData objects are created indirectly, by subclasses of ImageType.
    @see Image, ImageType

    @tags{Graphics}
*/
class JUCE_API  ImagePixelData  : public ReferenceCountedObject
{
public:
    ImagePixelData (Image::PixelFormat, int width, int height);
    ~ImagePixelData() override;

    using Ptr = ReferenceCountedObjectPtr<ImagePixelData>;

    /** Creates a context that will draw into this image. */
    virtual std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() = 0;
    /** Creates a copy of this image. */
    virtual Ptr clone() = 0;
    /** Creates an instance of the type of this image. */
    virtual std::unique_ptr<ImageType> createType() const = 0;
    /** Initialises a BitmapData object. */
    virtual void initialiseBitmapData (Image::BitmapData&, int x, int y, Image::BitmapData::ReadWriteMode) = 0;
    /** Returns the number of Image objects which are currently referring to the same internal
        shared image data. This is different to the reference count as an instance of ImagePixelData
        can internally depend on another ImagePixelData via it's member variables. */
    virtual int getSharedCount() const noexcept;


    /** The pixel format of the image data. */
    const Image::PixelFormat pixelFormat;
    const int width, height;

    /** User-defined settings that are attached to this image.
        @see Image::getProperties().
    */
    NamedValueSet userData;

    //==============================================================================
    /** Used to receive callbacks for image data changes */
    struct Listener
    {
        virtual ~Listener() = default;

        virtual void imageDataChanged (ImagePixelData*) = 0;
        virtual void imageDataBeingDeleted (ImagePixelData*) = 0;
    };

    ListenerList<Listener> listeners;

    void sendDataChangeMessage();

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePixelData)
};

//==============================================================================
/**
    This base class is for handlers that control a type of image manipulation format,
    e.g. an in-memory bitmap, an OpenGL image, CoreGraphics image, etc.

    @see SoftwareImageType, NativeImageType, OpenGLImageType

    @tags{Graphics}
*/
class JUCE_API  ImageType
{
public:
    ImageType();
    virtual ~ImageType();

    /** Creates a new image of this type, and the specified parameters. */
    virtual ImagePixelData::Ptr create (Image::PixelFormat, int width, int height, bool shouldClearImage) const = 0;

    /** Must return a unique number to identify this type. */
    virtual int getTypeID() const = 0;

    /** Returns an image which is a copy of the source image, but using this type of storage mechanism.
        For example, to make sure that an image is stored in-memory, you could use:
        @code myImage = SoftwareImageType().convert (myImage); @endcode
    */
    virtual Image convert (const Image& source) const;
};

//==============================================================================
/**
    An image storage type which holds the pixels in-memory as a simple block of values.
    @see ImageType, NativeImageType

    @tags{Graphics}
*/
class JUCE_API  SoftwareImageType   : public ImageType
{
public:
    SoftwareImageType();
    ~SoftwareImageType() override;

    ImagePixelData::Ptr create (Image::PixelFormat, int width, int height, bool clearImage) const override;
    int getTypeID() const override;
};

//==============================================================================
/**
    An image storage type which holds the pixels using whatever is the default storage
    format on the current platform.
    @see ImageType, SoftwareImageType

    @tags{Graphics}
*/
class JUCE_API  NativeImageType   : public ImageType
{
public:
    NativeImageType();
    ~NativeImageType() override;

    ImagePixelData::Ptr create (Image::PixelFormat, int width, int height, bool clearImage) const override;
    int getTypeID() const override;
};

} // namespace juce
