invicnaper/MWF

View on GitHub
Cpp/rawimage.cpp

Summary

Maintainability
Test Coverage
#include "cppw_rawimage.h"

#include "cppw_file.h"

#include <cstdlib>
#include <cmath>

void CWHSV::ToRGB(CWRGB* pRGB) const
{
    uint8_t region, remainder, p, q, t;

    if (Saturation == 0)
    {
        pRGB->Red = Value;
        pRGB->Green = Value;
        pRGB->Blue = Value;
        return;
    }

    region = Hue / 43;
    remainder = (Hue - (region * 43)) * 6;

    p = (Value * (255 - Saturation)) >> 8;
    q = (Value * (255 - ((Saturation * remainder) >> 8))) >> 8;
    t = (Value * (255 - ((Saturation * (255 - remainder)) >> 8))) >> 8;

    switch (region)
    {
        case 0:
            pRGB->Red = Value; pRGB->Green = t; pRGB->Blue = p;
            break;
        case 1:
            pRGB->Red = q; pRGB->Green = Value; pRGB->Blue = p;
            break;
        case 2:
            pRGB->Red = p; pRGB->Green = Value; pRGB->Blue = t;
            break;
        case 3:
            pRGB->Red = p; pRGB->Green = q; pRGB->Blue = Value;
            break;
        case 4:
            pRGB->Red = t; pRGB->Green = p; pRGB->Blue = Value;
            break;
        default:
            pRGB->Red = Value; pRGB->Green = p; pRGB->Blue = q;
            break;
    }
}

void CWRGB::ToHSV(CWHSV* pHSV) const
{
    uint8_t rgbMin, rgbMax;

    rgbMin = Red < Green ? (Red < Blue ? Red : Blue) : (Green < Blue ? Green : Blue);
    rgbMax = Red > Green ? (Red > Blue ? Red : Blue) : (Green > Blue ? Green : Blue);

    pHSV->Value = rgbMax;
    if (pHSV->Value == 0)
    {
        pHSV->Hue = 0;
        pHSV->Saturation = 0;
        return;
    }

    pHSV->Saturation = 255 * long(rgbMax - rgbMin) / pHSV->Value;
    if (pHSV->Saturation == 0)
    {
        pHSV->Hue = 0;
        return;
    }

    if (rgbMax == Red)
        pHSV->Hue = 0 + 43 * (Green - Blue) / (rgbMax - rgbMin);
    else if (rgbMax == Green)
        pHSV->Hue = 85 + 43 * (Blue - Red) / (rgbMax - rgbMin);
    else
        pHSV->Hue = 171 + 43 * (Red - Green) / (rgbMax - rgbMin);
}

// -----------------
// -----------------


CWHorzLine::CWHorzLine(int iStartX, int iEndX, int iY)
{
    StartX = iStartX;
    EndX = iEndX;

    Y = iY;
}

bool CWHorzLine::Contains(int iX, int iY) const
{
    return iY == Y && iX >= StartX && iX <= EndX;
}

bool CWHorzLine::Touches(int iX, int iY) const
{
    return (iY >= Y - 1 && iY <= Y + 1) && (iX >= StartX - 1 && iX <= EndX + 1);
}

bool CWHorzLine::Touches(CWHorzLine* pLine) const
{
    return Touches(pLine->StartX, pLine->Y) || Touches(pLine->EndX, pLine->Y);
}


// -----------------
// -----------------


CWSelection::CWSelection(bool bOwnsObjects)
{
    OwnsObjects = bOwnsObjects;

    Rect.Set();

    tmp = 0;
}

int CWSelection::Count() const
{
    return Items.size();
}

void CWSelection::Clear()
{
    if(OwnsObjects)
    {
        int count = Count();
        for(int i = 0; i < count; i++)
            delete Items[i];
    }
    Items.clear();
    GetRect(&Rect);
}

void CWSelection::Add(CWHorzLine* pLine)
{
    bool was_empty = Count() == 0;

    Items.push_back(pLine);

    if(was_empty)
        GetRect(&Rect);
    else
        Rect.Merge(pLine->StartX, pLine->Y, pLine->EndX, pLine->Y);
}

void CWSelection::Add(int iStartX, int iEndX, int iY)
{
    bool was_empty = Count() == 0;

    Add(new CWHorzLine(iStartX, iEndX, iY));

    if(was_empty)
        GetRect(&Rect);
    else
        Rect.Merge(iStartX, iY, iEndX, iY);
}

void CWSelection::Append(CWSelection* pSelection)
{
    bool was_empty = Count() == 0;

    int count = pSelection->Count();
    for(int i = 0; i < count; i++)
    {
        CWHorzLine* line = pSelection->Items[i];
        Add(line->StartX, line->EndX, line->Y);
    }

    if(was_empty)
        GetRect(&Rect);
    else
        Rect.Merge(&(pSelection->Rect));
}

void CWSelection::Delete(int iIndex)
{
    if(OwnsObjects) delete Items[iIndex];
    Items.erase(Items.begin() + iIndex);

    GetRect(&Rect);
}

bool CWSelection::Touches(CWHorzLine* pLine) const
{
    if(!Rect.Touches(pLine->StartX, pLine->Y, pLine->EndX, pLine->Y))
        return false;

    int count = Count();
    for(int i = 0; i < count; i++)
    {
        if(Items[i]->Touches(pLine))
            return true;
    }
    return false;
}

bool CWSelection::Touches(CWSelection* pSelection) const
{
    if(!Rect.Touches(&pSelection->Rect))
        return false;

    int count = Count();
    for(int i = 0; i < count; i++)
    {
        if(pSelection->Touches(Items[i]))
            return true;
    }
    return false;
}

void CWSelection::SplitIntoObjects(CWSelectionList* pList)
{
    pList->Clear();
    int count = Count();
    CWSelection* belongs_to = NULL;
    for(int i = 0; i < count; i++)
    {
        CWHorzLine* line = Items[i];

        if(belongs_to == NULL || !belongs_to->Touches(line))
            belongs_to = pList->FindTouchingSelection(line);

        if(belongs_to == NULL)
        {
            belongs_to = new CWSelection();
            pList->Add(belongs_to);
        }
        belongs_to->Add(line->StartX, line->EndX, line->Y);
    }

    pList->JoinTouchingSelections();
}

void CWSelection::GetRect(CWRect* pRect) const
{
    pRect->TopX = 0;
    pRect->TopY = 0;
    pRect->BottomX = 0;
    pRect->BottomY = 0;

    int count = Count();
    for(int i = 0; i < count; i++)
    {
        CWHorzLine* line = Items[i];

        if(i == 0)
        {
            pRect->TopX = line->StartX;
            pRect->TopY = line->Y;
            pRect->BottomX = line->EndX;
            pRect->BottomY = line->Y;
        }
        else
        {
            if(line->StartX < pRect->TopX) pRect->TopX = line->StartX;
            if(line->EndX > pRect->BottomX) pRect->BottomX = line->EndX;
            if(line->Y < pRect->TopY) pRect->TopY = line->Y;
            if(line->Y > pRect->BottomY) pRect->BottomY = line->Y;
        }
    }
}

// -----------------
// -----------------


CWSelectionList::CWSelectionList(bool bOwnsObjects)
{
    OwnsObjects = bOwnsObjects;
}

int CWSelectionList::Count() const
{
    return Items.size();
}

void CWSelectionList::Clear()
{
    if(OwnsObjects)
    {
        int count = Count();
        for(int i = 0; i < count; i++)
            delete Items[i];
    }
    Items.clear();
}

void CWSelectionList::Add(CWSelection* pSelection)
{
    Items.push_back(pSelection);
}

void CWSelectionList::Delete(int iIndex)
{
    if(OwnsObjects) delete Items[iIndex];
    Items.erase(Items.begin() + iIndex);
}

CWSelection* CWSelectionList::FindTouchingSelection(CWHorzLine* pLine) const
{
    int count = Count();
    for(int i = 0; i < count; i++)
    {
        if(Items[i]->Touches(pLine))
            return Items[i];
    }
    return NULL;
}

void CWSelectionList::JoinTouchingSelections(int iStartIndex)
{
    int count = Count();
    for(int i = iStartIndex; i < count; i++)
    {
        for(int x = count - 1; x >= 0; x--)
        {
            if(x != i)
            {
                if(Items[x]->Touches(Items[i]))
                {
                    Items[i]->Append(Items[x]);
                    Delete(x);
                    count--;
                    if(x < i)
                        i--;
                }
            }
        }
    }
}

void CWSelectionList::Swap(int iIndex1, int iIndex2)
{
    std::swap(Items[iIndex1], Items[iIndex2]);
}

void CWSelectionList::SortByArea(int iStart, int iEnd)
{
    int i = iStart;
    int k = iEnd;

    if(iEnd - iStart >= 1)
    {
        while(k > i)
        {
            while(Items[i]->tmp <= Items[iStart]->tmp && i <= iEnd && k > i)
                i++;
            while(Items[k]->tmp > Items[iStart]->tmp && k >= iStart && k >= i)
                k--;

            if(k > i)
            {
                if(Items[i]->tmp != Items[k]->tmp)
                    Swap(i, k);
            }
        }

        if(Items[iStart]->tmp != Items[k]->tmp)
            Swap(iStart, k);

        if(k > iStart)
            SortByArea(iStart, k - 1);
        if(k < iEnd)
            SortByArea(k + 1, iEnd);
    }
}

void CWSelectionList::SortByArea()
{
    int count = Count();
    for(int i = 0; i < count; i++)
        Items[i]->tmp = Items[i]->Rect.Area();

    SortByArea(0, Count() - 1);
}

// -----------------
// -----------------

CWRect::CWRect(int iTopX, int iTopY, int iBottomX, int iBottomY)
{
    Set(iTopX, iTopY, iBottomX, iBottomY);

    tmp = 0;
}

void CWRect::Set(int iTopX, int iTopY, int iBottomX, int iBottomY)
{
    TopX = iTopX;
    TopY = iTopY;
    BottomX = iBottomX;
    BottomY = iBottomY;
}

bool CWRect::Intersects(int iTopX, int iTopY, int iBottomX, int iBottomY) const
{
    return TopX <= iBottomX && BottomX >= iTopX && TopY <= iBottomY && BottomY >= iTopY;
}

bool CWRect::Intersects(CWRect* pRect) const
{
    return Intersects(pRect->TopX, pRect->TopY, pRect->BottomX, pRect->BottomY);
}

bool CWRect::Touches(int iTopX, int iTopY, int iBottomX, int iBottomY) const
{
    return TopX <= iBottomX + 1 && BottomX >= iTopX - 1 && TopY <= iBottomY + 1 && BottomY >= iTopY - 1;
}

bool CWRect::Touches(CWRect* pRect) const
{
    return Touches(pRect->TopX, pRect->TopY, pRect->BottomX, pRect->BottomY);
}

bool CWRect::Contains(int iX, int iY) const
{
    return iX >= TopX && iX <= BottomX && iY >= TopY && iY <= BottomY;
}

void CWRect::Merge(int iTopX, int iTopY, int iBottomX, int iBottomY, bool* pIsExpanded)
{
    if(pIsExpanded != NULL) *pIsExpanded = false;

    if(TopX > iTopX) { TopX = iTopX; if(pIsExpanded != NULL) *pIsExpanded = true; }
    if(BottomX < iBottomX) { BottomX = iBottomX; if(pIsExpanded != NULL) *pIsExpanded = true; }

    if(TopY > iTopY) { TopY = iTopY; if(pIsExpanded != NULL) *pIsExpanded = true; }
    if(BottomY < iBottomY) { BottomY = iBottomY; if(pIsExpanded != NULL) *pIsExpanded = true; }
}

void CWRect::Merge(CWRect* pRect, bool* pIsExpanded)
{
    Merge(pRect->TopX, pRect->TopY, pRect->BottomX, pRect->BottomY, pIsExpanded);
}

int CWRect::Width() const
{
    return (BottomX - TopX) + 1;
}

int CWRect::Height() const
{
    return (BottomY - TopY) + 1;
}

int CWRect::Area() const
{
    return Width() * Height();
}


// -----------------
// -----------------


CWRectList::CWRectList()
{

}

CWRectList::~CWRectList()
{
    Clear();
}

int CWRectList::Count()
{
    return Items.size();
}

void CWRectList::Clear()
{
    int count = Count();
    for(int i = 0; i < count; i++)
        delete Items[i];
    Items.clear();
}

void CWRectList::Add(CWRect* pRect)
{
    Items.push_back(pRect);
}

void CWRectList::Add(int iTopX, int iTopY, int iBottomX, int iBottomY)
{
    Add(new CWRect(iTopX, iTopY, iBottomX, iBottomY));
}

void CWRectList::Delete(int iIndex)
{
    delete Items[iIndex];
    Items.erase(Items.begin() + iIndex);
}

void CWRectList::Swap(int iIndex1, int iIndex2)
{
    std::swap(Items[iIndex1], Items[iIndex2]);
}

void CWRectList::SortByArea(int iStart, int iEnd)
{
    int i = iStart;
    int k = iEnd;

    if(iEnd - iStart >= 1)
    {
        while(k > i)
        {
            while(Items[i]->tmp <= Items[iStart]->tmp && i <= iEnd && k > i)
                i++;
            while(Items[k]->tmp > Items[iStart]->tmp && k >= iStart && k >= i)
                k--;

            if(k > i)
            {
                if(Items[i]->tmp != Items[k]->tmp)
                    Swap(i, k);
            }
        }

        if(Items[iStart]->tmp != Items[k]->tmp)
            Swap(iStart, k);

        if(k > iStart)
            SortByArea(iStart, k - 1);
        if(k < iEnd)
            SortByArea(k + 1, iEnd);
    }
}

void CWRectList::SortByArea()
{
    int count = Count();
    for(int i = 0; i < count; i++)
        Items[i]->tmp = Items[i]->Area();

    SortByArea(0, Count() - 1);
}

// -----------------
// -----------------


CWRawImage::CWRawImage()
{
    Buffer = NULL;
}

CWRawImage::~CWRawImage()
{
    Free();
}

void CWRawImage::Free()
{
    if(Buffer != NULL)
    {
        delete Buffer;
        Buffer = NULL;
    }
}

bool CWRawImage::LoadFromFile(string sFileName, uint32_t iWidth, uint32_t iHeight, uint8_t iPixelFormat, string* pErrorMessage)
{
    Free();

    uint8_t* buffer = NULL;
    uint32_t file_size = 0;
    if(!FileLoad(sFileName, (void**)&buffer, (int*)&file_size, 0, pErrorMessage))
        return false;

    uint32_t width = iWidth;
    uint32_t height = iHeight;
    uint8_t pixel_format = iPixelFormat;

    uint32_t buffpos = 0;
    if(file_size >= 12 && buffer[0] == 'J' && buffer[1] == 'B' && buffer[2] == 'G')
    {
        // has JBG header
        buffpos = 3;

        memcpy(&width, buffer + buffpos, sizeof(uint32_t)); buffpos += sizeof(uint32_t);
        memcpy(&height, buffer + buffpos, sizeof(uint32_t)); buffpos += sizeof(uint32_t);
        memcpy(&pixel_format, buffer + buffpos, sizeof(uint8_t)); buffpos += sizeof(uint8_t);
    }
    else
    {
        // nema header-a... da li velicina fajla odgovara rezoluciji?
        // ako je pixel format = 0 (24bit RGB888)
        if(file_size < (width * height * 3))
        {
            if(file_size < (width * height * 2))
            {
                if(pErrorMessage != NULL) *pErrorMessage = "Invalid file size.";
                free(buffer);
                return false;
            }
            else
                pixel_format = 1; // ako je fajl manji od w*h*3, a veci ili jednak w*h*2, pretpostavljamo da je 16bit RGB565
        }
    }

    Buffer = (uint8_t*)malloc(width * height * 3);
    if(Buffer == NULL)
    {
        if(pErrorMessage != NULL) *pErrorMessage = "Out of memory.";
        free(buffer);
        return false;
    }

    if(pixel_format == 0)
    {
        // already RGB888
        memcpy(Buffer, buffer + buffpos, width * height * 3);
    }
    else
    {
        if(pixel_format == 1)
        {
            // convert RGB565 to RGB888
            uint16_t color = 0;
            int pos = 0;
            while(buffpos < file_size)
            {
                memcpy(&color, buffer + buffpos, sizeof(uint16_t)); buffpos += sizeof(uint16_t);
                // 5-6-5 dekodiranje slike (5 bitova RED, 6 bitova GREEN, 5 bitova BLUE
                Buffer[pos] = ((color & 63488) >> 11) * 8; pos++;
                Buffer[pos] = ((color & 2016) >> 5) * 4; pos++;
                Buffer[pos] = (color & 31) * 8; pos++;
            }
        }
    }

    Width = width;
    Height = height;

    free(buffer);
    return true;
}

bool CWRawImage::SaveToFile(string sFileName, uint8_t iPixelFormat, bool bJBGHeader, string* pErrorMessage)
{
    int fd = -1;
    if(!FileCreate(sFileName, &fd, pErrorMessage))
        return false;

    if(bJBGHeader)
    {
        // write 12-byte JBG header
        uint8_t* header = (uint8_t*)malloc(12);
        uint32_t hpos = 0;
        memcpy(header + hpos, "JBG", 3); hpos += 3;
        memcpy(header + hpos, &Width, sizeof(uint32_t)); hpos += sizeof(uint32_t);
        memcpy(header + hpos, &Height, sizeof(uint32_t)); hpos += sizeof(uint32_t);
        memcpy(header + hpos, &iPixelFormat, sizeof(uint8_t)); hpos += sizeof(uint8_t);

        if(!FileWrite(fd, header, 12, pErrorMessage))
        {
            free(header);
            FileClose(fd, NULL);
            return false;
        }
        free(header);
    }

    if(iPixelFormat == 0)
    {
        // Write internal buffer (already RGB888)
        if(!FileWrite(fd, Buffer, Width * Height * 3, pErrorMessage))
        {
            FileClose(fd, NULL);
            return false;
        }
    }
    else
    {
        // Convert to RGB565
        int num_pixels = Width * Height;
        int data_size = num_pixels * 2;
        uint8_t* data = (uint8_t*)malloc(data_size);

        int data_counter = 0;
        int buffer_counter = 0;
        for(int i = 0; i < num_pixels; i++)
        {
            unsigned char R = Buffer[buffer_counter++];
            unsigned char G = Buffer[buffer_counter++];
            unsigned char B = Buffer[buffer_counter++];

            B = (B * 31) / 255;
            G = (G * 63) / 255;
            R = (R * 31) / 255;

            data[data_counter++] = B | ((G & 7) << 5);
            data[data_counter++] = ((G & 56) >> 3) | ((R & 31) << 3);
        }

        // Write internal buffer (already RGB888)
        if(!FileWrite(fd, data, data_size, pErrorMessage))
        {
            free(data);
            FileClose(fd, NULL);
            return false;
        }
        free(data);
    }

    FileClose(fd, NULL);
    return true;
}

void CWRawImage::GetPixelRGB(uint32_t iX, uint32_t iY, CWRGB* pRGB)
{
    int buffpos = (Width * (iY * 3)) + (iX * 3);

    pRGB->Red = Buffer[buffpos];
    pRGB->Green = Buffer[buffpos + 1];
    pRGB->Blue = Buffer[buffpos + 2];
}

void CWRawImage::SetPixelRGB(uint32_t iX, uint32_t iY, const CWRGB& RGB)
{
    int buffpos = (Width * (iY * 3)) + (iX * 3);

    Buffer[buffpos] = RGB.Red;
    Buffer[buffpos + 1] = RGB.Green;
    Buffer[buffpos + 2] = RGB.Blue;
}

bool CWRawImage::IsPixelSimilar(uint32_t iX, uint32_t iY, const CWHSV& HSVColor, uint8_t iTolerHue, uint8_t iTolerSaturation, uint8_t iTolerValue)
{
    // get pixel RGB
    CWRGB RGB;
    GetPixelRGB(iX, iY, &RGB);

    // convert to HSV
    CWHSV HSV;
    RGB.ToHSV(&HSV);

    return abs(HSV.Hue - HSVColor.Hue) <= iTolerHue && abs(HSV.Saturation - HSVColor.Saturation) <= iTolerSaturation && abs(HSV.Value - HSVColor.Value) <= iTolerValue;
}

void CWRawImage::SelectColor(const CWRect& Rect, const CWRGB& RGBColor, uint8_t iTolerHue, uint8_t iTolerSaturation, uint8_t iTolerValue, CWSelectionList* pSelectionList)
{
    CWHSV HSV;
    RGBColor.ToHSV(&HSV);

    CWSelection selection;

    // extract matching lines
    CWHorzLine* current_line = NULL;
    for(uint32_t y = Rect.TopY; y <= Rect.BottomY; y++)
    {
        current_line = NULL;
        for(uint32_t x = Rect.TopX; x <= Rect.BottomX; x++)
        {
            if(IsPixelSimilar(x, y, HSV, iTolerHue, iTolerSaturation, iTolerValue))
            {
                if(current_line == NULL)
                {
                    current_line = new CWHorzLine(x, x, y);
                    selection.Add(current_line);
                }
                else
                    current_line->EndX++;
            }
            else
                current_line = NULL;
        }
    }

    pSelectionList->Clear();
    selection.SplitIntoObjects(pSelectionList);
}

void CWRawImage::AverageColor(const CWRect& rect, CWRGB* pRGB)
{
    uint32_t numpix = rect.Width() * rect.Height();
    if(numpix == 0)
    {
        pRGB->Red = 0;
        pRGB->Green = 0;
        pRGB->Blue = 0;
        return;
    }

    int32_t sum_r = 0;
    int32_t sum_g = 0;
    int32_t sum_b = 0;

    for(int y = rect.TopY; y <= rect.BottomY; y++)
    {
        for(int x = rect.TopX; x <= rect.BottomX; x++)
        {
            int buffpos = (Width * (y * 3)) + (x * 3);
            sum_r += Buffer[buffpos];
            sum_g += Buffer[buffpos + 1];
            sum_b += Buffer[buffpos + 2];
        }
    }

    pRGB->Red = sum_r / numpix;
    pRGB->Green = sum_g / numpix;
    pRGB->Blue = sum_b / numpix;
}

void CWRawImage::DrawHorzLine(int iStartX, int iEndX, int iY, const CWRGB& rgb)
{
    int buffpos = (Width * (iY * 3)) + (iStartX * 3);

    int length = iEndX - iStartX + 1;
    for(int i = 0; i < length; i++)
    {
        Buffer[buffpos] = rgb.Red; buffpos++;
        Buffer[buffpos] = rgb.Green; buffpos++;
        Buffer[buffpos] = rgb.Blue; buffpos++;
    }
}

void CWRawImage::DrawHorzLine(const CWHorzLine& line, const CWRGB& rgb)
{
    DrawHorzLine(line.StartX, line.EndX, line.Y, rgb);
}

void CWRawImage::DrawHorzLine(CWHorzLine* pLine, const CWRGB& rgb)
{
    DrawHorzLine(pLine->StartX, pLine->EndX, pLine->Y, rgb);
}

void CWRawImage::FillRect(const CWRect& rect, const CWRGB& rgb)
{
    for(int y = rect.TopY; y <= rect.BottomY; y++)
        DrawHorzLine(rect.TopX, rect.BottomX, y, rgb);
}

void CWRawImage::FillSelection(const CWSelection& selection, const CWRGB& rgb)
{
    int count = selection.Count();
    for(int i = 0; i < count; i++)
    {
        CWHorzLine* line = selection.Items[i];
        DrawHorzLine(line, rgb);
    }
}

void CWRawImage::DrawPixmap(const string& sPixmap, uint32_t iX, uint32_t iY, const CWRGB& rForeground, const CWRGB& rBackground, bool bTransparentFG, bool bTransparentBG, int* pWidth, int* pHeight)
{
    if(bTransparentFG && bTransparentBG)
        return;

    int width = 0;
    int height = 0;

    int x = iX;
    int y = iY;
    int counter = 0;
    while(sPixmap[counter] != '\0')
    {
        while(sPixmap[counter] == '\r')
        {
            x = iX;
            y++;
            counter++;
            if(sPixmap[counter] == '\n')
                counter++;
        }

        while(sPixmap[counter] == '\n')
        {
            x = iX;
            y++;
            counter++;
            if(sPixmap[counter] == '\r')
                counter++;
        }

        if(sPixmap[counter] != '\0')
        {
            if(x - iX > width) width = (x - iX) + 1;
            if(y - iY > height) height = (y - iY) + 1;

            if(sPixmap[counter] == '0' && !bTransparentBG)
                SetPixelRGB(x, y, rBackground);
            else
            {
                if(sPixmap[counter] == '1' && !bTransparentFG)
                    SetPixelRGB(x, y, rForeground);
            }
            x++;
            counter++;
        }
    }

    if(pWidth != NULL) *pWidth = width;
    if(pHeight != NULL) *pHeight = height;
}

void CWRawImage::Pixelize(const CWRect& Rect, uint32_t iPixelWidth, uint32_t iPixelHeight)
{
    CWRGB rgb;
    for(uint32_t y = Rect.TopY; y <= Rect.BottomY; y += iPixelHeight)
    {
        for(uint32_t x = Rect.TopX; x <= Rect.BottomX; x += iPixelWidth)
        {
            CWRect rect(x, y, x + iPixelWidth - 1, y + iPixelHeight - 1);
            if(rect.BottomX >= (int)Width) rect.BottomX = Width - 1;
            if(rect.BottomY >= (int)Height) rect.BottomY = Height - 1;

            AverageColor(rect, &rgb);

            // fill
            FillRect(rect, rgb);
        }
    }
}

void CWRawImage::Treshold(const CWRect& Rect, const CWRGB& RGBColor, uint8_t iTolerHue, uint8_t iTolerSaturation, uint8_t iTolerValue)
{
    CWHSV HSV;
    RGBColor.ToHSV(&HSV);

    CWRGB rgb_black(0, 0, 0);
    CWRGB rgb_white(255, 255, 255);
    for(uint32_t y = Rect.TopY; y <= Rect.BottomY; y++)
    {
        for(uint32_t x = Rect.TopX; x <= Rect.BottomX; x++)
        {
            if(IsPixelSimilar(x, y, HSV, iTolerHue, iTolerSaturation, iTolerValue))
                SetPixelRGB(x, y, rgb_white);
            else
                SetPixelRGB(x, y, rgb_black);
        }
    }
}

void CWRawImage::Desaturate(const CWRect& Rect)
{
    CWRGB rgb;
    CWHSV hsv;
    for(uint32_t y = Rect.TopY; y <= Rect.BottomY; y++)
    {
        for(uint32_t x = Rect.TopX; x <= Rect.BottomX; x++)
        {
            GetPixelRGB(x, y, &rgb);
            rgb.ToHSV(&hsv);
            rgb.Set(hsv.Value, hsv.Value, hsv.Value);
            SetPixelRGB(x, y, rgb);
        }
    }
}