{******************************************************************************
*******************************************************************************
** This source is provided 'as is', the author is not liable for any data    **
** loss or damage due to use of this system.                                 **
**                                                                           **
** Copyright (C) 2000, Lawrence Blue B. Cuenca & Marivie A. Galeon           **
** All Rights Reserved.                                                      **
**                                                                           **
** May be used freely provided that the author is acknowledged anywhere in   **
** the system and this header is not removed. Please email any modifications,**
** improvements, comments and reactions to blue@nexmil.net.                  **
**                                                                           **
** This unit consists of procedures to: (1) Flip the bitmap horizontally;    **
**   (2) Flip the bitmap horizontally, (3) to Rotate the bitmap counter-     **
**    clockwise by increments of 90 degrees.                                 **
**                                                                           **
**	 Currently, bitmaps format supported is 1bit, 4Bit, 8Bit, 15Bit,         **
**	 16Bit, 24bit, 32Bit, and Device-specific.                               **
**                                                                           **
** Added: (29 February 2000)                                                 **
**        - Support for pfDevice PixelFormat                                 **
**            if PixelFormat is pfDevice, the routines will check the        **
**            current device color depth and use that info.                  **
**        - Added some comments to explain some of the code                  **
**                                                                           **
*******************************************************************************
*******************************************************************************}

unit ImgFlip;

interface
uses Windows, Classes, SysUtils, Graphics;

type
	EInvalidPixelFormat = class(Exception);

procedure ImageFlipH(aBitmap: TBitmap);
// flips the image along the center horizontal axis

procedure ImageFlipV(aBitmap: TBitmap);
// flips the image along the center vertical axis

procedure ImageRotate90(aBitmap: TBitmap);
// rotates the bitmap counterclockwise in increments of 90degrees

implementation

const
	BitsPerByte = 8; // Assume there are 8 bits in one byte, this may not be true in the future. (JIC)

{ this functions returns the number of bytes needed to represent 1 pixel
	in the current PixelFormat of the bitmap. }
function GetPixelSize(aBitmap: TBitmap): integer;
var
	nBitCount, nMultiplier: integer;
begin
	case aBitmap.PixelFormat of
		pfDevice:
			begin
				nBitCount := GetDeviceCaps(aBitmap.Canvas.Handle, BITSPIXEL);
				nMultiplier := nBitCount div BitsPerByte;
				if (nBitCount mod BitsPerByte)>0 then
				begin
					Inc(nMultiplier);
				end;
			end;
		pf1bit: nMultiplier := 1;
		pf4bit: nMultiplier := 1;
		pf8bit: nMultiplier := 1;
		pf15bit: nMultiplier := 2;
		pf16bit: nMultiplier := 2;
		pf24bit: nMultiplier := 3;
		pf32bit: nMultiplier := 4;
		else raise EInvalidPixelFormat.Create('Bitmap pixelformat is unknown.');
	end;
	Result := nMultiplier;
end;

procedure ImageFlipH(aBitmap: TBitmap);
var
	nMultiplier,
	nMemSize, y, y2,
	nHalfHeight, nFullHeight: integer;
	aScanLine: PByteArray;

begin
	nMultiplier := GetPixelSize(ABitmap);

	nMemSize := aBitmap.Width * nMultiplier;
	GetMem(aScanLine, nMemSize);
	try

		nFullHeight := aBitmap.Height;
		nHalfHeight := nFullHeight div 2;

		{
			This FOR-LOOP basically swaps the top and the bottom lines converging
			into the center lines.
		}
		for y := 0 to nHalfHeight do
		begin
			Move(aBitmap.ScanLine[y]^, aScanLine^, nMemSize);
			y2 := nFullHeight - y - 1;
			Move(aBitmap.ScanLine[y2]^, aBitmap.ScanLine[y]^, nMemSize);
			Move(aScanLine^, aBitmap.ScanLine[y2]^, nMemSize);
		end;

	finally
		FreeMem(aScanLine);
	end;
end;

{
This procedure swaps the pixel information in a scanline, in other words, the
leftmost pixel is swapped with the rightmost pixel moving to the middle of the
scanline.  nSize is the total byte size of the scanline.  nElemWidth is the
number of bytes per pixel. (e.g. for 24bit, 1 pixel is 3 bytes)
}
procedure ReverseScanLine(AScanLine: PByteArray; nSize, nElemWidth: integer);
var
	i, j, w, w2, x1, x2: integer;
	aByte: Byte;
begin
	w := nSize div nElemWidth;
	w2 := w div 2;
	for i := 0 to w2 do
	begin
		x1 := i * nElemWidth;
		x2 := (w - i - 1) * nElemWidth;
		for j := 0 to nElemWidth-1 do
		begin
			aByte := AScanLine[x1 + j];
			AScanLine[x1 + j] := AScanLine[x2 + j];
			AScanLine[x2 + j] := aByte;
		end;
	end;
end;

procedure ImageFlipV(aBitmap: TBitmap);
var
	nMultiplier,
	nMemSize, y,
	nFullHeight: integer;
	aScanLine: PByteArray;
begin

	nMultiplier := GetPixelSize(ABitmap);

	nMemSize := aBitmap.Width * nMultiplier;
	GetMem(aScanLine, nMemSize);
	try

		nFullHeight := aBitmap.Height;

		{ this FOR-LOOP read the scanlines into a buffer and swaps the pixels and
			then writes it back into the bitmap scanlines.  the buffer is necessary
			because it is faster working with a copy of the scanline than using the
			scanline itself }
		for y := 0 to nFullHeight-1 do
		begin
			Move(aBitmap.ScanLine[y]^, aScanLine^, nMemSize);
			ReverseScanLine(aScanLine, nMemSize, nMultiplier);
			Move(aScanLine^, aBitmap.ScanLine[y]^, nMemSize);
		end;

	finally
		FreeMem(aScanLine);
	end;
end;

{ this procedure rotates the image counterclockwise by 90 degrees.
	i could have made this routine rotate clockwise but doing it in
	counter-clockwise is easier and thus less prone to bugs.

	what we do here is grab the first scanline and distribute each pixel
	along one column of the new image (or in this case new scanlines buffer)
	we do this until we run out of scanlines then we are done.}
procedure ImageRotate90(aBitmap: TBitmap);
var
	nIdx, nOfs,
	x, y, i,
	nMultiplier: integer;
	nMemWidth, nMemHeight, nMemSize,
	nScanLineSize: LongInt;

	aScnLnBuffer: PChar;
	aScanLine: PByteArray;
begin

	nMultiplier := GetPixelSize(ABitmap);

	nMemWidth := aBitmap.Height;
	nMemHeight := aBitmap.Width;
	nMemSize := nMemWidth * nMemHeight * nMultiplier;

	GetMem(aScnLnBuffer, nMemSize);
	try
		nScanLineSize := aBitmap.Width * nMultiplier;
		GetMem(aScanLine, nScanLineSize);
		try
			for y := 0 to aBitmap.Height-1 do
			begin
				Move(aBitmap.ScanLine[y]^, aScanLine^, nScanLineSize);
				for x := 0 to aBitmap.Width-1 do
				begin
					nIdx := ((aBitmap.Width-1) - x) * nMultiplier;
					nOfs := (x * nMemWidth * nMultiplier) +  // y component of the dst
										(y * nMultiplier); // x component of the dst
					for i := 0 to nMultiplier-1 do
						Byte(aScnLnBuffer[nOfs + i]) := aScanLine[nIdx+i];
				end;
			end;

			aBitmap.Height := nMemHeight;
			aBitmap.Width := nMemWidth;

			for y := 0 to nMemHeight-1 do
			begin
				nOfs := y * nMemWidth * nMultiplier;
				Move((@(aScnLnBuffer[nOfs]))^, aBitmap.ScanLine[y]^, nMemWidth * nMultiplier);
			end;

		finally
			FreeMem(aScanLine, nScanLineSize);
		end;
	finally
		FreeMem(aScnLnBuffer, nMemSize);
	end;

end;

end.
