From: "Jack Sudarev" To: Subject: Rotate bitmap Date: Thursday, June 22, 2000 10:23 PM Hello Earl, I`d like to say thank you for your wonderful site and make sort of contribution. A modified "Rotate bitmap using scanline" function which performs antialiasing is in attachment. Probably not the fastest one, but it`s working. Regards, Jack Sudarev ======================================================================= //Rotate bitmap from TImage to any angle procedure RotateBitmap(ImageToChange: TImage; ReferenceImage: TImage; Angle: SmallInt; const CameraNumber: Integer; AntiAliasing: Boolean); var cosTheta, sinTheta: Double; i, j: Integer; iRotationAxis, jRotationAxis: Integer; iOriginal, jOriginal: Integer; iPrime, jPrime: Integer; iPrimeRotated, jPrimeRotated: Integer; iPrimeRotatedA, jPrimeRotatedA: Double; iOriginalA, jOriginalA: Double; RowOriginal: pRGBArray; RowRotated: pRGBArray; Theta: Double; // angle in radians RotatedBitmap, OriginalBitmap: TBitmap; begin if Assigned(ImageToChange) then begin RotatedBitmap := TBitmap.Create; OriginalBitmap := ReferenceImage.Picture.Bitmap; if OriginalBitmap.PixelFormat <> pf24bit then OriginalBitmap.PixelFormat := pf24bit; // force to 24 bits try // The size of BitmapRotated is the same as BitmapOriginal. PixelFormat // must also match since 24-bit GBR triplets are assumed in ScanLine. RotatedBitmap.Width := OriginalBitmap.Width; RotatedBitmap.Height := OriginalBitmap.Height; RotatedBitmap.PixelFormat := pf24bit; // Axis of rotation is normally center of image iRotationAxis := RotatedBitmap.Width div 2; jRotationAxis := RotatedBitmap.Height div 2; // Convert degrees to radians. Use minus sign to force clockwise rotation. Theta := (- Angle) * Pi/180; sinTheta := Sin(Theta); cosTheta := Cos(Theta); // Step through each row of rotated image. for j := RotatedBitmap.Height - 1 downto 0 do begin RowRotated := RotatedBitmap.Scanline[j]; { Assume the bitmap has an even number of pixels in both dimensions and the axis of rotation is to be the exact middle of the image -- so this axis of rotation is not at the middle of any pixel. The transformation (i,j) to (iPrime, jPrime) puts the center of each pixel at odd-numbered coordinates. The left and right sides of each pixel (as well as the top and bottom) then have even-numbered coordinates. The point (iRotationAxis, jRotationAxis) identifies the axis of rotation. For a 640 x 480 pixel image, the center point is (320, 240). Pixels numbered (index i) 0..319 are left of this point along the "X" axis and pixels numbered 320..639 are right of this point. Likewise, vertically pixels are numbered (index j) 0..239 above the axis of rotation and 240..479 below the axis of rotation. The subtraction (i, j) - (iRotationAxis, jRotationAxis) moves the axis of rotation from (i, j) to (iRotationAxis, jRotationAxis), which is the center of the bitmap in this implementation. } jPrime := 2*(j - jRotationAxis) + 1; for i := RotatedBitmap.Width - 1 downto 0 do begin iPrime := 2*(i - iRotationAxis) + 1; // Rotate (iPrime, jPrime) to location of desired pixel // Note: There is negligible difference between floating point and // scaled integer arithmetic here, so keep the math simple (and readable). if not AntiAliasing then begin iPrimeRotated := Round(iPrime * CosTheta - jPrime * sinTheta); jPrimeRotated := Round(iPrime * sinTheta + jPrime * cosTheta); // Transform back to pixel coordinates of image, including translation // of origin from axis of rotation to origin of image. iOriginal := (iPrimeRotated - 1) div 2 + iRotationAxis; jOriginal := (jPrimeRotated - 1) div 2 + jRotationAxis; // Make sure (iOriginal, jOriginal) is in BitmapOriginal. If not, // assign white color to corner points. if (iOriginal >= 0) and (iOriginal <= OriginalBitmap.Width - 1) and (jOriginal >= 0) and (jOriginal <= OriginalBitmap.Height - 1) then begin // Assign pixel from rotated space to current pixel in BitmapRotated RowOriginal := OriginalBitmap.Scanline[jOriginal]; RowRotated[i] := RowOriginal[iOriginal] end else begin RowRotated[i].rgbtBlue := 255; // assign "corner" color RowRotated[i].rgbtGreen := 255; RowRotated[i].rgbtRed := 255; end; end else begin //Antialiasing is On iPrimeRotatedA := iPrime * CosTheta - jPrime * sinTheta; jPrimeRotatedA := iPrime * sinTheta + jPrime * cosTheta; // Transform back to pixel coordinates of image, including translation // of origin from axis of rotation to origin of image. iOriginalA := (iPrimeRotatedA - 1)/2 + iRotationAxis; jOriginalA := (jPrimeRotatedA - 1)/2 + jRotationAxis; // Make sure (iOriginal, jOriginal) is in BitmapOriginal. If not, // assign white color to corner points. if (iOriginalA >= 0) and (iOriginalA <= OriginalBitmap.Width - 1) and (jOriginalA >= 0) and (jOriginalA <= OriginalBitmap.Height - 1) then begin // Assign pixel from rotated space to current pixel in BitmapRotated RowRotated[i] := GetSmoothColor(iOriginalA, jOriginalA, OriginalBitmap); end else begin RowRotated[i].rgbtRed := 255; RowRotated[i].rgbtGreen := 255; RowRotated[i].rgbtBlue := 255; // assign "corner" color end; end; end;//for i end;//for j AddCameraNumber(RotatedBitmap, CameraNumber); ImageToChange.Picture.Graphic := RotatedBitmap; finally RotatedBitmap.Free; end; end;//valid input data end; //Add camera number to camera image procedure AddCameraNumber(RotatedBitmap: TBitmap; const CameraNumber: Integer); var CameraString: string; OldFont: TFont; StringSize: TSize; TextStart: TPoint; begin if Assigned(RotatedBitmap) then begin //Save canvas font OldFont := RotatedBitmap.Canvas.Font; CameraString := Format('%.3d', [CameraNumber]); //Switch to small font RotatedBitmap.Canvas.Font.Name := 'Small Fonts'; RotatedBitmap.Canvas.Font.Size := 7; //Get the sting size StringSize := RotatedBitmap.Canvas.TextExtent(CameraString); //Define the point from which start to paint the text TextStart.x := 16 - StringSize.cx div 2; if TextStart.x < 0 then TextStart.x := 0; TextStart.y := 16 - StringSize.cy div 2; if TextStart.y < 0 then TextStart.y := 0; RotatedBitmap.Canvas.TextOut(TextStart.x, TextStart.y, CameraString); //Restore the font RotatedBitmap.Canvas.Font := OldFont; end; end; //Bilinear interpolation function GetSmoothColor(iOriginal, jOriginal: Double; OriginalBitmap: TBitmap): TRGBTriple; var f0, f1, f2, f3: Double; P0, P1, P2, P3: TRGBTriple; P : pRGBArray; begin if Assigned(OriginalBitmap) then begin //Get fractional parts f0 := (1 - Frac(iOriginal))*(1 - Frac(jOriginal)); f1 := Frac(iOriginal)*(1 - Frac(jOriginal)); f2 := Frac(iOriginal)*Frac(jOriginal); f3 := (1 - Frac(iOriginal))*Frac(jOriginal); //Get surrounding points P := OriginalBitmap.ScanLine[Trunc(jOriginal)]; P0 := P[Trunc(iOriginal)]; P1 := P[Ceil(iOriginal)]; P := OriginalBitmap.ScanLine[Ceil(jOriginal)]; P2 := P[Trunc(iOriginal)]; P3 := P[Ceil(iOriginal)]; //Calculate result color Result.rgbtRed := Round(P0.rgbtRed*f0 + P1.rgbtRed*f1 + P2.rgbtRed*f2 + P3.rgbtRed*f3); Result.rgbtGreen := Round(P0.rgbtGreen*f0 + P1.rgbtGreen*f1 + P2.rgbtGreen*f2 + P3.rgbtGreen*f3); Result.rgbtBlue := Round(P0.rgbtBlue*f0 + P1.rgbtBlue*f1 + P2.rgbtBlue*f2 + P3.rgbtBlue*f3); end; end;