Friday, 29 January 2010

Batch Watermark with Delphi part I

As a novice photographer, always upload my pictures on flickr and the other day someone told me about putting watermarks to the pictures just in case (in order to protect my pictures). Well, said and done, here you can find one of my last applications: Thundax batch watermark. A simple application that lets you upload a set of jpeg images and a bitmap watermark and then embed the mark in each of the images by merging the pixels.

Imagine that we have this picture:


And We've designed the following watermark:

Then with my application, we can merge this pictures in an aesthetical way:

And the result:

The way to achieve this is by playing with the TJPEGImage and TBitmap classes. Here you can see the source code that I've used to implement this solution:

procedure BatchPictures();
var
  jpg: TJPEGImage;
  bmp: TBitmap;
  i: integer;
  sFile: string;
begin
  for i := 0 to ListView1.Items.Count - 1 do
    begin
      if ListView1.Items[i].Checked then
      begin
        bmp := TBitmap.Create;
        jpg := TJPEGImage.Create;
        jpg.LoadFromFile(ListView1.Items[i].Caption);
        bmp.Assign(jpg);
        jpg.Destroy;
        bmp.PixelFormat := pf24bit;
        AddWatermark(bmp, SpinEdit1.Value / 100);
        Jpg := TJPEGImage.Create;
        Jpg.Assign(Bmp);
        sFile := ExtractFileName(ListView1.Items[i].Caption);
        jpg.SaveToFile(Edit1.text + '\out' + sFile);
        bmp.Destroy;
        jpg.Destroy;
      end;
    end;
end;


the function:

procedure AddToImageWaterMark(bitmap: TBitmap; alpha: single);
procedure ColorToRGB(iColor: TColor; var R, G, B: Byte);
function HexToInt(const Value: string): Integer;
begin
Result := StrToInt('$' + Value);
end;
var
s: string;
begin
s := inttohex(iColor, 6);
R := HexToInt(AnsiRightStr(s, 2));
G := HexToInt(AnsiLeftStr(AnsiRightStr(s, 4), 2));
B := HexToInt(AnsiLeftStr(s, 2));
end;
type
TRGB = array[0..1023] of TRGBTriple;
var
waterMark: TBitmap;
sourceX, sourceY, distanceX, distanceY: integer;
linesDestination, linesSource: ^TRGB;
R,G,B : Byte;
begin
waterMark := TBitmap.Create;
waterMark.LoadFromFile(Edit2.text);

distanceY := 0;
distanceX := 0;

if rightBottom.Checked then
distanceY := bitmap.Height - waterMark.Height - 10
else if LeftTop.Checked then
distanceY := 10
else if righttop.Checked then
distanceY := 10
else if leftBottom.Checked then
distanceY := bitmap.Height - waterMark.Height - 10;

for sourceY := 0 to waterMark.Height - 1 do
begin
linesSource := waterMark.ScanLine[sourceY];
linesDestination := bitmap.ScanLine[distanceY];

if rightBottom.Checked then
distanceX := bitmap.Width - waterMark.Width - 10
else if LeftTop.Checked then
distanceX := 10
else if righttop.Checked then
distanceX := bitmap.Width - waterMark.Width - 10
else if leftBottom.Checked then
distanceX := 10;

for sourceX := 0 to waterMark.Width - 1 do
begin
ColorToRGB(ColorBox1.Selected, R, G, B);
if (linesSource[sourceX].rgbtRed = R)
and (linesSource[sourceX].rgbtGreen = G)
and (linesSource[sourceX].rgbtBlue = B) then
begin
linesDestination[distanceX].rgbtRed := linesDestination[distanceX].rgbtRed;
linesDestination[distanceX].rgbtGreen := linesDestination[distanceX].rgbtGreen;
linesDestination[distanceX].rgbtBlue := linesDestination[distanceX].rgbtBlue;
end
else
begin
linesDestination[distanceX].rgbtRed := trunc(linesDestination[distanceX].rgbtRed * (1 - alpha) + linesSource[sourceX].rgbtRed * alpha);
linesDestination[distanceX].rgbtGreen := trunc(linesDestination[distanceX].rgbtGreen * (1 - alpha) + linesSource[sourceX].rgbtGreen * alpha);
linesDestination[distanceX].rgbtBlue := trunc(linesDestination[distanceX].rgbtBlue * (1 - alpha) + linesSource[sourceX].rgbtBlue * alpha);
end;
inc(distanceX);
end;
distanceY := distanceY + 1;
end;
waterMark.Destroy;
end;

end.


I hope you enjoy!. Comments are welcome.!

5 comments:

  1. Let me ask, in the else statement "if (linesSource[sourceX].rgbtRed = R) ... ", does the first begin/end block make any sense? It looks like you are updating RGBTriplets with them selves ?
    Why not just:

    if not ( (linesSource[sourceX].rgbtRed = R)
    and (linesSource[sourceX].rgbtGreen = G)
    and(linesSource[sourceX].rgbtBlue = B) )
    then begin
    linesDestination[distanceX].rgbtRed := trunc(linesDestination[distanceX].rgbtRed * (1 - alpha) + linesSource[sourceX].rgbtRed * alpha);
    linesDestination[distanceX].rgbtGreen := trunc(linesDestination[distanceX].rgbtGreen * (1 - alpha) + linesSource[sourceX].rgbtGreen * alpha);
    linesDestination[distanceX].rgbtBlue := trunc(linesDestination[distanceX].rgbtBlue * (1 - alpha) + linesSource[sourceX].rgbtBlue * alpha);
    end;

    or making 3 if/else independently one for each of R,G,B :

    //var PSrc,PDst: ^TRGBTriple;
    for sourceX := 0 to waterMark.Width - 1 do
    begin
    ColorToRGB(MaskColor, R, G, B);
    PSrc := linesSource[sourceX];
    PDst := linesDestination[distanceX];
    if(PSrc^.rgbtRed <> R)then PDst^.rgbtRed := trunc(PDst^.rgbtRed * (1 - alpha) + PSrc^.rgbtRed * alpha);
    if(PSrc^.rgbtGreen <> G)then PDst^.rgbtGreen := trunc(PDst^.rgbtGreen * (1 - alpha) + PSrc^.rgbtGreen * alpha);
    if(PSrc^.rgbtBlue <> B)then PDst^.rgbtBlue := trunc(PDst^.rgbtBlue * (1 - alpha) + PSrc^.rgbtBlue * alpha);
    inc(distanceX);
    end;

    ReplyDelete
    Replies
    1. Hi Krzysztof,

      You are right, that could be refactored and improved. Your suggestion could work. I did that for a reason but I can't remember why.It must be that I assign the same colour again as I don't want to alter the colour when I get the same RGB value.

      Jordi

      Delete
  2. also, ColorToRGB(MaskColor, R, G, B); happens inside of the second leve for loop, and it executes soo many times. You should call it only once per entire function, somewhere outside of any loop, don't you think?

    ReplyDelete
    Replies
    1. Hi Krzysztof,

      The same goes to that method. It can be moved to an outer scope.

      Jordi

      Delete
  3. I asked for a file that already happens. please.

    ReplyDelete