1

I was writing a function to shrink a source image (JPG file) according to a 170 x 200px picture. The source JPG image was loaded into a TImage (Image1, fixed size of 400 x 400px, stretched to fit with aspect ration maintained), then the user will make a selection rectangle to set the area to copy, and then the image will be copied using CopyRect() onto the destination TImage (Image2).

void __fastcall TSizePhotoForm::Button3Click(TObject *Sender)
{
    float scale, base  = 400.0f;
    TRect crect; // copy rect

    Image2->Width  = 170;
    Image2->Height = 200;

    Image2->Canvas->CopyMode = cmSrcCopy;
    TJPEGImage *img = new TJPEGImage();
    img->LoadFromFile(fname);
    Graphics::TBitmap *bmp = new Graphics::TBitmap;
    bmp->Assign(img);
    scale = (float)img->Width / base;
    crect.Left   = srect.Left * scale; // srect = source rect
    crect.Top    = srect.Top  * scale;
    crect.Right  = crect.Left + (srect.Width()  * scale);
    crect.Bottom = crect.Top  + (srect.Height() * scale);
    Image2->Canvas->CopyRect(TRect(0, 0, w, h), bmp->Canvas, crect);
    delete img;
    delete bmp;
}

The problem is, the resulting image color is not right, and I observed that the larger the source image, the resulting image color shifting is more worse.

Here is the screenshot of the result: Color shifted

Any Idea what's wrong and how do I get rid of this color shifting problem? Thanks in advance.

Community
  • 1
  • 1
Kw Choy
  • 129
  • 1
  • 1
  • 10
  • `CopyRect` is not good in changing scale much better is `StretchDraw` instead (but you might need an temporary bitmap for it with target size if not covering the whole canvas). The problem might be also in the target pixel format try to set it to 24 or 32 bit instead of 8 – Spektre Dec 07 '17 at 08:12
  • Thanks for your suggestion, but I don't know how to do what you suggested. I need to copy a portion of the source image to the destination, StretchDraw will copy the whole source image and shrink/stretch it on the destination image. I also don't know how I can change the pixel format of a TImage or TBitmap, I couldn't find any method or property in these objects related to pixel format. – Kw Choy Dec 08 '17 at 04:56
  • I think I recreated your problem and it was both pixel format and `CopyRect` .... see my answer – Spektre Dec 08 '17 at 08:49

1 Answers1

1

Ok I tried to recreate your problem And it looks your Image2 pixel format is the problem. I assume you got pf8bit set and jpg is pf24bit so it truncates to greenish... Also Copy rectangle is choppy and StretchDraw is way much smoother. Here comparison:

And the updated code of yours (Assuming Image1 is the source left image and Image2 the zoom image in the right):

preview

As you can see the StretchDraw is the best not sure why they do not use the same Scaling techniques.

int h,w;
float scale, base  = 400.0f;
TRect crect; // copy rect
TRect srect; // I assume some mouse selected rectangle I set it manualy instead
// init and load
Image2->Canvas->CopyMode = cmSrcCopy;
TJPEGImage *img = new TJPEGImage();
img->LoadFromFile("in.jpg");
Graphics::TBitmap *bmp = new Graphics::TBitmap;
bmp->Assign(img);
// I assume this is how you are rendering/store your left (source) image
Image1->Width  = bmp->Width;            // set dersired size
Image1->Height = bmp->Height;
Image1->Left   = 10;
Image1->Top    = 10;
Image1->Canvas->Draw(0,0,bmp);
// I assume this is your mouse selection
srect=TRect(90,34,192,154);
h=120; w=102;
// just render into Image1 for visual check
Image1->Canvas->Pen->Color=clYellow;
Image1->Canvas->Pen->Style=psDashDot;
Image1->Canvas->Brush->Style=bsClear;
Image1->Canvas->Rectangle(srect);
Image1->Canvas->Pen->Style=psSolid;
Image1->Canvas->Brush->Style=bsSolid;
// place and resize Image2 next to Image1
Image2->Top=10;
Image2->Left=Image1->Width+20;
Image2->Width  = 170;
Image2->Height = 200;
// scaling
scale = (float)img->Width / base;
crect.Left   = srect.Left * scale; // srect = source rect
crect.Top    = srect.Top  * scale;
crect.Right  = crect.Left + (srect.Width()  * scale);
crect.Bottom = crect.Top  + (srect.Height() * scale);
// this is how you change the pixelformat
Image2->Picture->Bitmap->PixelFormat=pf32bit;

// your copy rect alternative
// Image2->Canvas->CopyRect(TRect(0,0,h,w), bmp->Canvas, crect);

// my stretch draw alternative
Graphics::TBitmap *tmp=new Graphics::TBitmap;
tmp->PixelFormat=pf32bit;
tmp->SetSize(srect.Width(),srect.Height());
tmp->Canvas->CopyRect(TRect(0,0,srect.Width(),srect.Height()), bmp->Canvas, srect);
Image2->Canvas->StretchDraw(TRect(0,0,srect.Width(),srect.Height()),tmp);
delete tmp;

// exit
delete img;
delete bmp;

If you want to know more about bitmap pixel format and fast direct pixel format see:

Also take in mind that Assign and LoadFrom... calls change the pixel format ...

The 8bit preview image is a bit different from yours but I did not have your input image instead I use the screenshot you posted encoded as PNG cropped and reencode as JPG so on per pixel basis there are most likelly diferences in color and may be even in position +/-1px

As you noticed the larger the area the more distortions in color. That is due to that on 8bit pixel formats you got only 256 colors in the palette and if your image contains more colors more of them got truncated. The bigger the selected area the more pixels and hence distinct colors are present in the image. GDI has a bit poor color quantization resulting in what you see... If you want something better (in case you using 8bit images as output) try these:

Also as you can see the CopyRect did not fit the selection rectangle most likely due to the scaling truncation (looks like they do some poor integer math instead of subdivided DDA or bi-linear filtering to "optimize" the speed)

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Thanks for your answer, it helped me understand the problem better. I have found a solution after reading your first comment. I create a temporary bitmap, set it to the same size as the selected area on the original image, copy the area from the jpg image to the temporary bitmap using CopyRect without shrinking, then StretchDraw to Image2. The result is satisfactory. – Kw Choy Dec 09 '17 at 08:44