root/src/servers/app/drawing/Painter/bitmap_painter/BitmapPainter.cpp
/*
 * Copyright 2009, Christian Packmann.
 * Copyright 2008, Andrej Spielmann <andrej.spielmann@seh.ox.ac.uk>.
 * Copyright 2005-2014, Stephan Aßmus <superstippi@gmx.de>.
 * Copyright 2015, Julian Harnath <julian.harnath@rwth-aachen.de>
 * All rights reserved. Distributed under the terms of the MIT License.
 */
#include "BitmapPainter.h"

#include <Bitmap.h>

#include <agg_image_accessors.h>
#include <agg_pixfmt_rgba.h>
#include <agg_span_image_filter_rgba.h>

#include "DrawBitmapBilinear.h"
#include "DrawBitmapGeneric.h"
#include "DrawBitmapNearestNeighbor.h"
#include "DrawBitmapNoScale.h"
#include "drawing_support.h"
#include "ServerBitmap.h"
#include "SystemPalette.h"


// #define TRACE_BITMAP_PAINTER
#ifdef TRACE_BITMAP_PAINTER
#       define TRACE(x...)              printf(x)
#else
#       define TRACE(x...)
#endif


Painter::BitmapPainter::BitmapPainter(const Painter* painter,
        const ServerBitmap* bitmap, uint32 options)
        :
        fPainter(painter),
        fStatus(B_NO_INIT),
        fOptions(options)
{
        if (bitmap == NULL || !bitmap->IsValid())
                return;

        fBitmapBounds = bitmap->Bounds();
        fBitmapBounds.OffsetBy(-fBitmapBounds.left, -fBitmapBounds.top);
                // Compensate for the lefttop offset the bitmap bounds might have
                // It has the right size, but put it at B_ORIGIN

        fColorSpace = bitmap->ColorSpace();

        fBitmap.attach(bitmap->Bits(), bitmap->Width(), bitmap->Height(),
                bitmap->BytesPerRow());

        fStatus = B_OK;
}


void
Painter::BitmapPainter::Draw(const BRect& sourceRect,
        const BRect& destinationRect)
{
        using namespace BitmapPainterPrivate;

        if (fStatus != B_OK)
                return;

        TRACE("BitmapPainter::Draw()\n");
        TRACE("   bitmapBounds = (%.1f, %.1f) - (%.1f, %.1f)\n",
                fBitmapBounds.left, fBitmapBounds.top,
                fBitmapBounds.right, fBitmapBounds.bottom);
        TRACE("   sourceRect = (%.1f, %.1f) - (%.1f, %.1f)\n",
                sourceRect.left, sourceRect.top,
                sourceRect.right, sourceRect.bottom);
        TRACE("   destinationRect = (%.1f, %.1f) - (%.1f, %.1f)\n",
                destinationRect.left, destinationRect.top,
                destinationRect.right, destinationRect.bottom);

        bool success = _DetermineTransform(sourceRect, destinationRect);
        if (!success)
                return;

        if ((fOptions & B_TILE_BITMAP) == 0) {
                // optimized version for no scale in CMAP8 or RGB32 OP_OVER
                if (!_HasScale() && !_HasAffineTransform() && !_HasAlphaMask()) {
                        if (fColorSpace == B_CMAP8) {
                                if (fPainter->fDrawingMode == B_OP_COPY) {
                                        DrawBitmapNoScale<CMap8Copy> drawNoScale;
                                        drawNoScale.Draw(fPainter->fInternal, fBitmap, 1, fOffset,
                                                fDestinationRect);
                                        return;
                                }
                                if (fPainter->fDrawingMode == B_OP_OVER) {
                                        DrawBitmapNoScale<CMap8Over> drawNoScale;
                                        drawNoScale.Draw(fPainter->fInternal, fBitmap, 1, fOffset,
                                                fDestinationRect);
                                        return;
                                }
                        } else if (fColorSpace == B_RGB32) {
                                if (fPainter->fDrawingMode == B_OP_OVER) {
                                        DrawBitmapNoScale<Bgr32Over> drawNoScale;
                                        drawNoScale.Draw(fPainter->fInternal, fBitmap, 4, fOffset,
                                                fDestinationRect);
                                        return;
                                }
                        }
                }
        }

        ObjectDeleter<BBitmap> convertedBitmapDeleter;
        _ConvertColorSpace(convertedBitmapDeleter);

        if ((fOptions & B_TILE_BITMAP) == 0) {
                // optimized version if there is no scale
                if (!_HasScale() && !_HasAffineTransform() && !_HasAlphaMask()) {
                        if (fPainter->fDrawingMode == B_OP_COPY) {
                                DrawBitmapNoScale<Bgr32Copy> drawNoScale;
                                drawNoScale.Draw(fPainter->fInternal, fBitmap, 4, fOffset,
                                        fDestinationRect);
                                return;
                        }
                        if (fPainter->fDrawingMode == B_OP_OVER
                                || (fPainter->fDrawingMode == B_OP_ALPHA
                                         && fPainter->fAlphaSrcMode == B_PIXEL_ALPHA
                                         && fPainter->fAlphaFncMode == B_ALPHA_OVERLAY)) {
                                DrawBitmapNoScale<Bgr32Alpha> drawNoScale;
                                drawNoScale.Draw(fPainter->fInternal, fBitmap, 4, fOffset,
                                        fDestinationRect);
                                return;
                        }
                }

                if (!_HasScale() && !_HasAffineTransform() && _HasAlphaMask()) {
                        if (fPainter->fDrawingMode == B_OP_COPY) {
                                DrawBitmapNoScale<Bgr32CopyMasked> drawNoScale;
                                drawNoScale.Draw(fPainter->fInternal, fBitmap, 4, fOffset,
                                        fDestinationRect);
                                return;
                        }
                }

                // bilinear and nearest-neighbor scaled, OP_COPY only
                if (fPainter->fDrawingMode == B_OP_COPY
                        && !_HasAffineTransform() && !_HasAlphaMask()) {
                        if ((fOptions & B_FILTER_BITMAP_BILINEAR) != 0) {
                                DrawBitmapBilinear<ColorTypeRgb, DrawModeCopy> drawBilinear;
                                drawBilinear.Draw(fPainter, fPainter->fInternal,
                                        fBitmap, fOffset, fScaleX, fScaleY, fDestinationRect);
                        } else {
                                DrawBitmapNearestNeighborCopy::Draw(fPainter, fPainter->fInternal,
                                        fBitmap, fOffset, fScaleX, fScaleY, fDestinationRect);
                        }
                        return;
                }

                if (fPainter->fDrawingMode == B_OP_ALPHA
                        && fPainter->fAlphaSrcMode == B_PIXEL_ALPHA
                        && fPainter->fAlphaFncMode == B_ALPHA_OVERLAY
                        && !_HasAffineTransform() && !_HasAlphaMask()
                        && (fOptions & B_FILTER_BITMAP_BILINEAR) != 0) {
                        DrawBitmapBilinear<ColorTypeRgba, DrawModeAlphaOverlay> drawBilinear;
                        drawBilinear.Draw(fPainter, fPainter->fInternal,
                                fBitmap, fOffset, fScaleX, fScaleY, fDestinationRect);
                        return;
                }
        }

        if ((fOptions & B_TILE_BITMAP) != 0) {
                DrawBitmapGeneric<Tile>::Draw(fPainter, fPainter->fInternal, fBitmap,
                        fOffset, fScaleX, fScaleY, fDestinationRect, fOptions);
        } else {
                // for all other cases (non-optimized drawing mode or scaled drawing)
                DrawBitmapGeneric<Fill>::Draw(fPainter, fPainter->fInternal, fBitmap,
                        fOffset, fScaleX, fScaleY, fDestinationRect, fOptions);
        }
}


bool
Painter::BitmapPainter::_DetermineTransform(BRect sourceRect,
        const BRect& destinationRect)
{
        if (!fPainter->fValidClipping
                || !sourceRect.IsValid()
                || ((fOptions & B_TILE_BITMAP) == 0
                        && !sourceRect.Intersects(fBitmapBounds))
                || !destinationRect.IsValid()) {
                return false;
        }

        fDestinationRect = destinationRect;

        if (!fPainter->fSubpixelPrecise) {
                align_rect_to_pixels(&sourceRect);
                align_rect_to_pixels(&fDestinationRect);
        }

        if((fOptions & B_TILE_BITMAP) == 0) {
                fScaleX = (fDestinationRect.Width() + 1) / (sourceRect.Width() + 1);
                fScaleY = (fDestinationRect.Height() + 1) / (sourceRect.Height() + 1);

                if (fScaleX == 0.0 || fScaleY == 0.0)
                        return false;

                // constrain source rect to bitmap bounds and transfer the changes to
                // the destination rect with the right scale
                if (sourceRect.left < fBitmapBounds.left) {
                        float diff = fBitmapBounds.left - sourceRect.left;
                        fDestinationRect.left += diff * fScaleX;
                        sourceRect.left = fBitmapBounds.left;
                }
                if (sourceRect.top < fBitmapBounds.top) {
                        float diff = fBitmapBounds.top - sourceRect.top;
                        fDestinationRect.top += diff * fScaleY;
                        sourceRect.top = fBitmapBounds.top;
                }
                if (sourceRect.right > fBitmapBounds.right) {
                        float diff = sourceRect.right - fBitmapBounds.right;
                        fDestinationRect.right -= diff * fScaleX;
                        sourceRect.right = fBitmapBounds.right;
                }
                if (sourceRect.bottom > fBitmapBounds.bottom) {
                        float diff = sourceRect.bottom - fBitmapBounds.bottom;
                        fDestinationRect.bottom -= diff * fScaleY;
                        sourceRect.bottom = fBitmapBounds.bottom;
                }
        } else {
                fScaleX = 1.0;
                fScaleY = 1.0;
        }

        fOffset.x = fDestinationRect.left - sourceRect.left;
        fOffset.y = fDestinationRect.top - sourceRect.top;

        return true;
}


bool
Painter::BitmapPainter::_HasScale()
{
        return fScaleX != 1.0 || fScaleY != 1.0;
}


bool
Painter::BitmapPainter::_HasAffineTransform()
{
        return !fPainter->fIdentityTransform;
}


bool
Painter::BitmapPainter::_HasAlphaMask()
{
        return fPainter->fInternal.fMaskedUnpackedScanline != NULL;
}


void
Painter::BitmapPainter::_ConvertColorSpace(
        ObjectDeleter<BBitmap>& convertedBitmapDeleter)
{
        if (fColorSpace == B_RGBA32)
                return;

        if (fColorSpace == B_RGB32
                && (fPainter->fDrawingMode == B_OP_COPY
#if 1
// Enabling this would make the behavior compatible to BeOS, which
// treats B_RGB32 bitmaps as B_RGB*A*32 bitmaps in B_OP_ALPHA - unlike in
// all other drawing modes, where B_TRANSPARENT_MAGIC_RGBA32 is handled.
// B_RGB32 bitmaps therefore don't draw correctly on BeOS if they actually
// use this color, unless the alpha channel contains 255 for all other
// pixels, which is inconsistent.
                || fPainter->fDrawingMode == B_OP_ALPHA
#endif
                )) {
                return;
        }

        BBitmap* conversionBitmap = new(std::nothrow) BBitmap(fBitmapBounds,
                B_BITMAP_NO_SERVER_LINK, B_RGBA32);
        if (conversionBitmap == NULL) {
                fprintf(stderr, "BitmapPainter::_ConvertColorSpace() - "
                        "out of memory for creating temporary conversion bitmap\n");
                return;
        }
        convertedBitmapDeleter.SetTo(conversionBitmap);

        status_t err = conversionBitmap->ImportBits(fBitmap.buf(),
                fBitmap.height() * fBitmap.stride(),
                fBitmap.stride(), 0, fColorSpace);
        if (err < B_OK) {
                fprintf(stderr, "BitmapPainter::_ConvertColorSpace() - "
                        "colorspace conversion failed: %s\n", strerror(err));
                return;
        }

        // the original bitmap might have had some of the
        // transaparent magic colors set that we now need to
        // make transparent in our RGBA32 bitmap again.
        switch (fColorSpace) {
                case B_RGB32:
                        _TransparentMagicToAlpha((uint32 *)fBitmap.buf(),
                                fBitmap.width(), fBitmap.height(),
                                fBitmap.stride(), B_TRANSPARENT_MAGIC_RGBA32,
                                conversionBitmap);
                        break;

                // TODO: not sure if this applies to B_RGBA15 too. It
                // should not because B_RGBA15 actually has an alpha
                // channel itself and it should have been preserved
                // when importing the bitmap. Maybe it applies to
                // B_RGB16 though?
                case B_RGB15:
                        _TransparentMagicToAlpha((uint16 *)fBitmap.buf(),
                                fBitmap.width(), fBitmap.height(),
                                fBitmap.stride(), B_TRANSPARENT_MAGIC_RGBA15,
                                conversionBitmap);
                        break;

                default:
                        break;
        }

        fBitmap.attach((uint8*)conversionBitmap->Bits(),
                (uint32)fBitmapBounds.IntegerWidth() + 1,
                (uint32)fBitmapBounds.IntegerHeight() + 1,
                conversionBitmap->BytesPerRow());
}


template<typename sourcePixel>
void
Painter::BitmapPainter::_TransparentMagicToAlpha(sourcePixel* buffer,
        uint32 width, uint32 height, uint32 sourceBytesPerRow,
        sourcePixel transparentMagic, BBitmap* output)
{
        uint8* sourceRow = (uint8*)buffer;
        uint8* destRow = (uint8*)output->Bits();
        uint32 destBytesPerRow = output->BytesPerRow();

        for (uint32 y = 0; y < height; y++) {
                sourcePixel* pixel = (sourcePixel*)sourceRow;
                uint32* destPixel = (uint32*)destRow;
                for (uint32 x = 0; x < width; x++, pixel++, destPixel++) {
                        if (*pixel == transparentMagic)
                                *destPixel &= 0x00ffffff;
                }

                sourceRow += sourceBytesPerRow;
                destRow += destBytesPerRow;
        }
}