{*******************************************************} { } { Tocsg.Graphic } { } { Copyright (C) 2023 kku } { } {*******************************************************} unit Tocsg.Graphic; interface uses Vcl.Graphics, System.SysUtils, Winapi.Windows, Vcl.Imaging.pngimage, Winapi.GDIPAPI; type PColorMatrix = ^TColorMatrix; function RotatePng(var aPng: TPNGImage; Angle: Extended): Boolean; function RotatePngFile(sSrcPath, sDestpath: String; Angle: Extended): Boolean; // RotateBitmap_STF() 이건 이미지 돌린 후 가로 넓이 AdjustSize 적용이 제대로 되지 않는다... 짤림 procedure RotateBitmap_STF(Bmp: Vcl.Graphics.TBitmap; Rads: Single; AdjustSize: Boolean; BkColor: TColor = clNone; nAddWidth: Integer = 0); procedure RotateBitmap_PlgBlt(Bmp: Vcl.Graphics.TBitmap; Rads: Single; AdjustSize: Boolean; BkColor: TColor = clNone); function ScalePercentBmp(bitmp: Vcl.Graphics.TBitmap; iPercent: Integer{100이면 원본}): Boolean; function MakeColorMatrix(R, G, B, A: Single): TColorMatrix; function MakeTransparentMatrix(A: Single): TColorMatrix; procedure InitializeGDIPlus; procedure FinalizeGDIPlus; function DrawBitmapWaterEx(aDestDC: HDC; nX, nY: Integer; aSrcBmp: Vcl.Graphics.TBitmap; pCM: PColorMatrix = nil; nStretchW: Integer = 0; nStretchH: Integer = 0): Boolean; function DrawBitmapWaterEx2(aDestDC: HDC; nX, nY: Integer; aSrcBmp: Vcl.Graphics.TBitmap; pCM: PColorMatrix = nil; nStretchW: Integer = 0; nStretchH: Integer = 0; fAngle: Single = 0): Boolean; function DrawBitmapWaterEx3( aDestDC: HDC; nX, nY: Integer; aSrcBmp: Vcl.Graphics.TBitmap; pCM: PColorMatrix = nil; nStretchW: Integer = 0; nStretchH: Integer = 0; fAngle: Single = 0 ): Boolean; implementation uses Tocsg.Safe, Tocsg.Exception, System.Classes, Winapi.GDIPOBJ, Winapi.ActiveX, System.UIConsts, System.Math; // 이걸로 돌리면 아래에 검은선이 생긴다. 25_0401 10:07:20 kku //function RotatePng(var aPng: TPNGImage; Angle: Extended): Boolean; // // {Supporting functions} // function TrimInt(i, Min, Max: Integer): Integer; // begin // if i>Max then Result:=Max // else if i255 then Result:=255 // else if i<0 then Result:=0 // else Result:=i; // end; // function Min(A, B: Double): Double; // begin // if A < B then Result := A else Result := B; // end; // function Max(A, B: Double): Double; // begin // if A > B then Result := A else Result := B; // end; // function Ceil(A: Double): Integer; // begin // Result := Integer(Trunc(A)); // if Frac(A) > 0 then // Inc(Result); // end; // // {Calculates the png new size} // function newsize: tsize; // var // fRadians: Extended; // fCosine, fSine: Double; // fPoint1x, fPoint1y, fPoint2x, fPoint2y, fPoint3x, fPoint3y: Double; // fMinx, fMiny, fMaxx, fMaxy: Double; // begin // {Convert degrees to radians} // fRadians := (2 * PI * Angle) / 360; // // fCosine := abs(cos(fRadians)); // fSine := abs(sin(fRadians)); // // fPoint1x := (-apng.Height * fSine); // fPoint1y := (apng.Height * fCosine); // fPoint2x := (apng.Width * fCosine - apng.Height * fSine); // fPoint2y := (apng.Height * fCosine + apng.Width * fSine); // fPoint3x := (apng.Width * fCosine); // fPoint3y := (apng.Width * fSine); // // fMinx := min(0,min(fPoint1x,min(fPoint2x,fPoint3x))); // fMiny := min(0,min(fPoint1y,min(fPoint2y,fPoint3y))); // fMaxx := max(fPoint1x,max(fPoint2x,fPoint3x)); // fMaxy := max(fPoint1y,max(fPoint2y,fPoint3y)); // // Result.cx := ceil(fMaxx-fMinx); // Result.cy := ceil(fMaxy-fMiny); // end; //type // TFColor = record b,g,r:Byte end; //var // Top, Bottom, Left, Right, eww,nsw, fx,fy, wx,wy: Extended; // cAngle, sAngle: Double; // xDiff, yDiff, ifx,ify, px,py, ix,iy, x,y, cx, cy: Integer; // nw,ne, sw,se: TFColor; // anw,ane, asw,ase: Byte; // P1,P2,P3:Pbytearray; // A1,A2,A3: pbytearray; // dst: TPNGImage; // IsAlpha: Boolean; // new_colortype: Integer; //begin // Result := false; // try // {Only allows RGB and RGBALPHA images} // if not (apng.Header.ColorType in [COLOR_RGBALPHA, COLOR_RGB]) then // raise Exception.Create('Only COLOR_RGBALPHA and COLOR_RGB formats' + // ' are supported'); // IsAlpha := apng.Header.ColorType in [COLOR_RGBALPHA]; // if IsAlpha then new_colortype := COLOR_RGBALPHA else // new_colortype := COLOR_RGB; // // {Creates a copy} // dst := tpngobject.Create; // with newsize do // dst.createblank(new_colortype, 8, cx, cy); // cx := dst.width div 2; cy := dst.height div 2; // // {Gather some variables} // Angle:=angle; // Angle:=-Angle*Pi/180; // sAngle:=Sin(Angle); // cAngle:=Cos(Angle); // xDiff:=(Dst.Width-apng.Width)div 2; // yDiff:=(Dst.Height-apng.Height)div 2; // // {Iterates over each line} // for y:=0 to Dst.Height-1 do // begin // P3:=Dst.scanline[y]; // if IsAlpha then A3 := Dst.AlphaScanline[y]; // py:=2*(y-cy)+1; // {Iterates over each column} // for x:=0 to Dst.Width-1 do // begin // px:=2*(x-cx)+1; // fx:=(((px*cAngle-py*sAngle)-1)/ 2+cx)-xDiff; // fy:=(((px*sAngle+py*cAngle)-1)/ 2+cy)-yDiff; // ifx:=Round(fx); // ify:=Round(fy); // // {Only continues if it does not exceed image boundaries} // if(ifx>-1)and(ifx-1)and(ify Max then Result := Max else Result := i; end; function IntToByte(i: Integer): Byte; begin if i < 0 then Result := 0 else if i > 255 then Result := 255 else Result := i; end; function CeilEx(A: Double): Integer; begin Result := Trunc(A); if Frac(A) > 0 then Inc(Result); end; function GetRotatedSize: TSize; var Radian, CosA, SinA: Extended; x1, y1, x2, y2, x3, y3, x4, y4: Extended; MinX, MaxX, MinY, MaxY: Extended; begin Radian := Angle * PI / 180; CosA := Abs(Cos(Radian)); SinA := Abs(Sin(Radian)); // 원래 이미지 꼭짓점 회전 후 좌표 계산 x1 := 0 * CosA - 0 * SinA; y1 := 0 * SinA + 0 * CosA; x2 := 0 * CosA - aPng.Height * SinA; y2 := 0 * SinA + aPng.Height * CosA; x3 := aPng.Width * CosA - 0 * SinA; y3 := aPng.Width * SinA + 0 * CosA; x4 := aPng.Width * CosA - aPng.Height * SinA; y4 := aPng.Width * SinA + aPng.Height * CosA; MinX := Min(Min(x1, x2), Min(x3, x4)); MinY := Min(Min(y1, y2), Min(y3, y4)); MaxX := Max(Max(x1, x2), Max(x3, x4)); MaxY := Max(Max(y1, y2), Max(y3, y4)); Result.cx := CeilEx(MaxX - MinX); Result.cy := CeilEx(MaxY - MinY); end; type TFColor = record r, g, b: Byte; end; var dst, src: TPNGImage; x, y, cx, cy, SrcW, SrcH, DstW, DstH: Integer; fx, fy, px, py: Extended; ifx, ify, ix, iy: Integer; Radian, SinA, CosA: Extended; eww, nsw: Extended; IsAlpha: Boolean; nw, ne, sw, se: TFColor; anw, ane, asw, ase: Byte; P1, P2, P3: PByteArray; A1, A2, A3: PByteArray; newColorType: Integer; begin Result := False; if not (aPng.Header.ColorType in [COLOR_RGB, COLOR_RGBALPHA]) then Exit; IsAlpha := aPng.Header.ColorType = COLOR_RGBALPHA; if IsAlpha then newColorType := COLOR_RGBALPHA else newColorType := COLOR_RGB; SrcW := aPng.Width; SrcH := aPng.Height; Radian := -Angle * PI / 180; SinA := Sin(Radian); CosA := Cos(Radian); // 원본 백업 src := TPNGImage.Create; src.Assign(aPng); // 새 이미지 생성 dst := TPNGImage.CreateBlank(newColorType, 8, GetRotatedSize.cx, GetRotatedSize.cy); DstW := dst.Width; DstH := dst.Height; cx := DstW div 2; cy := DstH div 2; for y := 0 to DstH - 1 do begin P3 := dst.ScanLine[y]; if IsAlpha then A3 := dst.AlphaScanline[y]; py := 2 * (y - cy) + 1; for x := 0 to DstW - 1 do begin px := 2 * (x - cx) + 1; fx := (((px * CosA - py * SinA) - 1) / 2 + SrcW / 2); fy := (((px * SinA + py * CosA) - 1) / 2 + SrcH / 2); ifx := Floor(fx); ify := Floor(fy); if (ifx >= 0) and (ifx < SrcW - 1) and (ify >= 0) and (ify < SrcH - 1) then begin eww := fx - ifx; nsw := fy - ify; ix := ifx + 1; iy := ify + 1; P1 := src.ScanLine[ify]; P2 := src.ScanLine[iy]; if IsAlpha then begin A1 := src.AlphaScanline[ify]; A2 := src.AlphaScanline[iy]; end; // 색상 복사 nw.r := P1[ifx * 3]; nw.g := P1[ifx * 3 + 1]; nw.b := P1[ifx * 3 + 2]; ne.r := P1[ix * 3]; ne.g := P1[ix * 3 + 1]; ne.b := P1[ix * 3 + 2]; sw.r := P2[ifx * 3]; sw.g := P2[ifx * 3 + 1]; sw.b := P2[ifx * 3 + 2]; se.r := P2[ix * 3]; se.g := P2[ix * 3 + 1]; se.b := P2[ix * 3 + 2]; if IsAlpha then begin anw := A1[ifx]; ane := A1[ix]; asw := A2[ifx]; ase := A2[ix]; end; // 보간 계산 P3[x * 3 + 2] := IntToByte(Round(nw.b + eww * (ne.b - nw.b) + nsw * ((sw.b + eww * (se.b - sw.b)) - (nw.b + eww * (ne.b - nw.b))))); P3[x * 3 + 1] := IntToByte(Round(nw.g + eww * (ne.g - nw.g) + nsw * ((sw.g + eww * (se.g - sw.g)) - (nw.g + eww * (ne.g - nw.g))))); P3[x * 3 + 0] := IntToByte(Round(nw.r + eww * (ne.r - nw.r) + nsw * ((sw.r + eww * (se.r - sw.r)) - (nw.r + eww * (ne.r - nw.r))))); if IsAlpha then A3[x] := IntToByte(Round(anw + eww * (ane - anw) + nsw * ((asw + eww * (ase - asw)) - (anw + eww * (ane - anw))))); end else begin // 경계 바깥은 흰색 또는 투명 처리 P3[x * 3 + 0] := 255; P3[x * 3 + 1] := 255; P3[x * 3 + 2] := 255; if IsAlpha then A3[x] := 0; end; end; end; aPng.Assign(dst); dst.Free; src.Free; Result := True; end; function RotatePngFile(sSrcPath, sDestpath: String; Angle: Extended): Boolean; var png: TPngImage; begin Result := false; try Guard(png, TPngImage.Create); png.LoadFromFile(sSrcPath); if RotatePng(png, Angle) then begin png.SaveToFile(sDestpath); Result := true; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. RotatePngFile()'); end; end; // SetWorldTransform procedure RotateBitmap_STF(Bmp: Vcl.Graphics.TBitmap; Rads: Single; AdjustSize: Boolean; BkColor: TColor = clNone; nAddWidth: Integer = 0); var C: Single; S: Single; XForm: tagXFORM; Tmp: Vcl.Graphics.TBitmap; begin C := Cos(Rads); S := Sin(Rads); XForm.eM11 := C; XForm.eM12 := S; XForm.eM21 := -S; XForm.eM22 := C; Tmp := Vcl.Graphics.TBitmap.Create; try Tmp.TransparentColor := Bmp.TransparentColor; Tmp.TransparentMode := Bmp.TransparentMode; Tmp.Transparent := Bmp.Transparent; Tmp.Canvas.Brush.Color := BkColor; if AdjustSize then begin Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S)) + nAddWidth; Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C)); XForm.eDx := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2; XForm.eDy := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2; end else begin Tmp.Width := Bmp.Width; Tmp.Height := Bmp.Height; XForm.eDx := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2; XForm.eDy := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2; end; SetGraphicsMode(Tmp.Canvas.Handle, GM_ADVANCED); SetWorldTransform(Tmp.Canvas.Handle, XForm); BitBlt(Tmp.Canvas.Handle, 0, 0, Tmp.Width, Tmp.Height, Bmp.Canvas.Handle, 0, 0, SRCCOPY); Bmp.Assign(Tmp); finally Tmp.Free; end; end; // PlgBlt procedure RotateBitmap_PlgBlt(Bmp: Vcl.Graphics.TBitmap; Rads: Single; AdjustSize: Boolean; BkColor: TColor = clNone); var C: Single; S: Single; Tmp: Vcl.Graphics.TBitmap; OffsetX: Single; OffsetY: Single; Points: array[0..2] of TPoint; begin try C := Cos(Rads); S := Sin(Rads); Tmp := Vcl.Graphics.TBitmap.Create; try Tmp.TransparentColor := Bmp.TransparentColor; Tmp.TransparentMode := Bmp.TransparentMode; Tmp.Transparent := Bmp.Transparent; Tmp.Canvas.Brush.Color := BkColor; if AdjustSize then begin Tmp.Width := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S)); Tmp.Height := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C)); OffsetX := (Tmp.Width - Bmp.Width * C + Bmp.Height * S) / 2; OffsetY := (Tmp.Height - Bmp.Width * S - Bmp.Height * C) / 2; end else begin Tmp.Width := Bmp.Width; Tmp.Height := Bmp.Height; OffsetX := (Bmp.Width - Bmp.Width * C + Bmp.Height * S) / 2; OffsetY := (Bmp.Height - Bmp.Width * S - Bmp.Height * C) / 2; end; Points[0].X := Round(OffsetX); Points[0].Y := Round(OffsetY); Points[1].X := Round(OffsetX + Bmp.Width * C); Points[1].Y := Round(OffsetY + Bmp.Width * S); Points[2].X := Round(OffsetX - Bmp.Height * S); Points[2].Y := Round(OffsetY + Bmp.Height * C); PlgBlt(Tmp.Canvas.Handle, Points, Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, 0, 0, 0); Bmp.Assign(Tmp); finally Tmp.Free; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. RotateBitmap_PlgBlt()'); end; end; function ScalePercentBmp(bitmp: Vcl.Graphics.TBitmap; iPercent: Integer{100이면 원본}): Boolean; var TmpBmp: Vcl.Graphics.TBitmap; ARect: TRect; h, w: Real; hi, wi: Integer; begin Result := False; try TmpBmp := Vcl.Graphics.TBitmap.Create; try // h := bitmp.Height * (iPercent / 100); // w := bitmp.Width * (iPercent / 100); // hi := StrToInt(FormatFloat('#', h)) + bitmp.Height; // wi := StrToInt(FormatFloat('#', w)) + bitmp.Width; hi := Round((iPercent / 100) * bitmp.Height); wi := Round((iPercent / 100) * bitmp.Width); TmpBmp.Width := wi; TmpBmp.Height := hi; ARect := System.Classes.Rect(0, 0, wi, hi); TmpBmp.Canvas.StretchDraw(ARect, Bitmp); bitmp.Assign(TmpBmp); finally TmpBmp.Free; end; Result := True; except {$IFDEF DEBUG} ASSERT(false); {$ENDIF} Result := False; end; end; function MakeColorMatrix(R, G, B, A: Single): TColorMatrix; begin Result[0, 0] := R; Result[0, 1] := 0; Result[0, 2] := 0; Result[0, 3] := 0; Result[0, 4] := 0; Result[1, 0] := 0; Result[1, 1] := G; Result[1, 2] := 0; Result[1, 3] := 0; Result[1, 4] := 0; Result[2, 0] := 0; Result[2, 1] := 0; Result[2, 2] := B; Result[2, 3] := 0; Result[2, 4] := 0; Result[3, 0] := 0; Result[3, 1] := 0; Result[3, 2] := 0; Result[3, 3] := A; Result[3, 4] := 0; Result[4, 0] := 0; Result[4, 1] := 0; Result[4, 2] := 0; Result[4, 3] := 0; Result[4, 4] := 1; end; function MakeTransparentMatrix(A: Single): TColorMatrix; begin ZeroMemory(@Result, SizeOf(Result)); Result[3, 3] := A; end; procedure InitializeGDIPlus; var StartupInput: TGdiplusStartupInput; begin if GDIPlusToken = 0 then begin StartupInput.GdiplusVersion := 1; StartupInput.DebugEventCallback := nil; StartupInput.SuppressBackgroundThread := False; StartupInput.SuppressExternalCodecs := False; if GdiplusStartup(GdiplusToken, @StartupInput, nil) <> Ok then exit; end; // raise Exception.Create('Failed to initialize GDI+'); end; procedure FinalizeGDIPlus; begin GdiplusShutdown(GdiplusToken); end; function MakePointF(X, Y: Single): TGPPointF; begin Result.X := X; Result.Y := Y; end; function DrawBitmapWaterEx(aDestDC: HDC; nX, nY: Integer; aSrcBmp: Vcl.Graphics.TBitmap; pCM: PColorMatrix = nil; nStretchW: Integer = 0; nStretchH: Integer = 0): Boolean; var GPGraphics: TGPGraphics; ms: TMemoryStream; // GPStream: IStream; GPImg: TGPImage; ImageAttributes: TGPImageAttributes; GPRect: TGPRect; nWW, nHH: Integer; Matrix: TGPMatrix; begin Result := true; try // Guard() 쓰면 Invalid Pointer 예외뜸 24_0411 13:44:29 kku ms := TMemoryStream.Create; // 얘는 (GPStream: IStream) 여기서 알아서 해제함 24_0411 13:51:35 kku aSrcBmp.SaveToStream(ms); ms.Position := 0; Guard(GPGraphics, TGPGraphics.Create(aDestDC)); GPGraphics.SetPageScale(0.33); // IStream 메모리 해제를 위해 여기에 변수 선언해야 함 24_0125 10:58:12 kku var GPStream: IStream := TStreamAdapter.Create(ms, soOwned) as IStream; Guard(GPImg, TGPImage.Create(GPStream)); Guard(ImageAttributes, TGPImageAttributes.Create); // 흰색 배경 투명처리 ImageAttributes.SetColorKey(System.UIConsts.MakeColor(255, 255, 255), System.UIConsts.MakeColor(255, 255, 255)); // 빨간색 배경 투명처리 // ImageAttributes.SetColorKey(System.UIConsts.MakeColor(255, 0, 0), System.UIConsts.MakeColor(255, 0, 0)); // ImageAttributes.SetColorMatrix(MakeColorMatrix(0.1, 0.1, 0.5, 0.2)); // Gray, 하늘색 // ImageAttributes.SetColorMatrix(MakeColorMatrix(0.1, 0.5, 0.5, 0.2)); // Gray, 녹색 // ImageAttributes.SetColorMatrix(MakeColorMatrix(0.1, 1.1, 0.5, 0.2)); // Gray, 연두색 // ImageAttributes.SetColorMatrix(MakeColorMatrix(0.2, 0.2, 0.2, 0.2)); // Gray, 실버 // ImageAttributes.SetColorMatrix(MakeColorMatrix(0.2, 0.2, 0.2, 0.3)); // Gray, 회색 if pCM <> nil then // ImageAttributes.SetColorMatrix(pCM^, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap) ImageAttributes.SetColorMatrix(pCM^) else ImageAttributes.SetColorMatrix(MakeColorMatrix(0.2, 0.2, 0.2, 0.3)); // Gray, 회색 // ImageAttributes.SetColorMatrix(MakeTransparentMatrix(0.4)); // 그 외 mode, type 옵션 의미 없음.. nWW := GPImg.GetWidth; nHH := GPImg.GetHeight; // var GPUnit: TUnit := GPGraphics.GetPageUnit; GPRect.X := nX; // nX div 2; // 이렇게 해야 위치가 맞음... 이유는 아직 모름 GPRect.Y := nY; // nY div 2; // 이렇게 해야 위치가 맞음... 이유는 아직 모름 GPRect.Width := nWW; GPRect.Height := nHH; if nStretchW > 0 then GPRect.Width := nStretchW; if nStretchH > 0 then GPRect.Height := nStretchH; GPGraphics.DrawImage(GPImg, GPRect, 0, 0, Round(nWW), Round(nHH), UnitPixel, ImageAttributes); GPGraphics.Flush(FlushIntentionFlush); except Result := false; end; end; function DrawBitmapWaterEx2(aDestDC: HDC; nX, nY: Integer; aSrcBmp: Vcl.Graphics.TBitmap; pCM: PColorMatrix = nil; nStretchW: Integer = 0; nStretchH: Integer = 0; fAngle: Single = 0): Boolean; var GPGraphics: TGPGraphics; ms: TMemoryStream; GPImg: TGPImage; ImageAttributes: TGPImageAttributes; GPRect: TGPRect; nWW, nHH: Integer; offX, offY: Integer; SavedDC: Integer; Matrix: TGPMatrix; begin Result := true; try SavedDC := SaveDC(aDestDC); try ms := TMemoryStream.Create; aSrcBmp.SaveToStream(ms); ms.Position := 0; Guard(GPGraphics, TGPGraphics.Create(aDestDC)); GPGraphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); // 보간법: 고품질 GPGraphics.SetSmoothingMode(SmoothingModeAntiAlias); // 안티앨리어싱 GPGraphics.SetPixelOffsetMode(PixelOffsetModeHighQuality); // 픽셀 오프셋 품질 // ★ SetPageScale 는 제거하는 걸 추천 (좌표 꼬임 방지) // GPGraphics.SetPageScale(0.33); // GPGraphics.SetPageUnit(UnitPixel); var GPStream: IStream := TStreamAdapter.Create(ms, soOwned) as IStream; Guard(GPImg, TGPImage.Create(GPStream)); Guard(ImageAttributes, TGPImageAttributes.Create); ImageAttributes.SetColorKey( System.UIConsts.MakeColor(255, 255, 255), System.UIConsts.MakeColor(255, 255, 255) ); if pCM <> nil then ImageAttributes.SetColorMatrix(pCM^) else ImageAttributes.SetColorMatrix(MakeColorMatrix(0.2, 0.2, 0.2, 0.3)); nWW := GPImg.GetWidth; nHH := GPImg.GetHeight; GPRect.X := nX; GPRect.Y := nY; GPRect.Width := nWW; GPRect.Height := nHH; if nStretchW > 0 then GPRect.Width := nStretchW; if nStretchH > 0 then GPRect.Height := nStretchH; // 프린터 물리 offset 구하기 offX := GetDeviceCaps(aDestDC, PHYSICALOFFSETX); offY := GetDeviceCaps(aDestDC, PHYSICALOFFSETY); GPGraphics.TranslateTransform(offX, offY); if fAngle <> 0 then begin Matrix := TGPMatrix.Create; try Matrix.RotateAt(fAngle, MakePointF(GPRect.X, GPRect.Y)); GPGraphics.SetTransform(Matrix); finally Matrix.Free; end; end; GPGraphics.DrawImage(GPImg, GPRect, 0, 0, nWW, nHH, UnitPixel, ImageAttributes); GPGraphics.Flush(FlushIntentionFlush); finally RestoreDC(aDestDC, SavedDC); end; except Result := false; end; end; function DrawBitmapWaterEx3(aDestDC: HDC; nX, nY: Integer; aSrcBmp: Vcl.Graphics.TBitmap; pCM: PColorMatrix = nil; nStretchW: Integer = 0; nStretchH: Integer = 0; fAngle: Single = 0): Boolean; var GPGraphics: TGPGraphics; ms: TMemoryStream; GPImg: TGPImage; ImageAttributes: TGPImageAttributes; GPRect: TGPRect; nWW, nHH: Integer; offX, offY: Integer; SavedDC: Integer; Matrix: TGPMatrix; begin Result := true; try SavedDC := SaveDC(aDestDC); try ms := TMemoryStream.Create; aSrcBmp.SaveToStream(ms); ms.Position := 0; Guard(GPGraphics, TGPGraphics.Create(aDestDC)); GPGraphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); // 보간법: 고품질 GPGraphics.SetSmoothingMode(SmoothingModeAntiAlias); // 안티앨리어싱 GPGraphics.SetPixelOffsetMode(PixelOffsetModeHighQuality); // 픽셀 오프셋 품질 // ★ SetPageScale 는 제거하는 걸 추천 (좌표 꼬임 방지) // GPGraphics.SetPageScale(0.33); GPGraphics.SetPageUnit(UnitPixel); var GPStream: IStream := TStreamAdapter.Create(ms, soOwned) as IStream; Guard(GPImg, TGPImage.Create(GPStream)); Guard(ImageAttributes, TGPImageAttributes.Create); ImageAttributes.SetColorKey( System.UIConsts.MakeColor(255, 255, 255), System.UIConsts.MakeColor(255, 255, 255) ); if pCM <> nil then ImageAttributes.SetColorMatrix(pCM^) else ImageAttributes.SetColorMatrix(MakeColorMatrix(0.2, 0.2, 0.2, 0.3)); nWW := GPImg.GetWidth; nHH := GPImg.GetHeight; GPRect.X := nX; GPRect.Y := nY; GPRect.Width := nWW; GPRect.Height := nHH; if nStretchW > 0 then GPRect.Width := nStretchW; if nStretchH > 0 then GPRect.Height := nStretchH; // 프린터 물리 offset 구하기 offX := GetDeviceCaps(aDestDC, PHYSICALOFFSETX); offY := GetDeviceCaps(aDestDC, PHYSICALOFFSETY); GPGraphics.TranslateTransform(offX, offY); if fAngle <> 0 then begin Matrix := TGPMatrix.Create; try Matrix.RotateAt(fAngle, MakePointF(GPRect.X, GPRect.Y)); GPGraphics.SetTransform(Matrix); finally Matrix.Free; end; end; GPGraphics.DrawImage(GPImg, GPRect, 0, 0, nWW, nHH, UnitPixel, ImageAttributes); GPGraphics.Flush(FlushIntentionFlush); finally RestoreDC(aDestDC, SavedDC); end; except Result := false; end; end; end.