Przycinanie obrazu (Crop)

1

Hej,

Chciałbym przedstawić pewien problem, z którym się obecnie borykam. Chodzi o "przycięcie obrazka" (Crop) do wymiaru wskazanego przez użytkownika.

Moje rozwiązanie jest maksymalnie proste. Program wyświetla obraz w TImage. Do zaznaczania ramki zaznaczenia używam TShape (choć jeśli ktoś zna dedykowany komponent - chętnie sprawdzę). Całość bazuje na obsłudze zdarzeń TImage. Obrazek wczytuje ze schowka (ale to nie jest ważne skąd).
MouseDown
MouseMove
MouseUp

Zasadniczo to działa, ale jest kilka problemów (trudno je opisać).

  1. Użytkownik wciskając mysz zaznacza punkt początkowy ramki i przesuwając go "rysuje" ramkę - kursor (koniec strzałki) jednak jest przesunięty względem tego punktu o pewną ilość pikseli (u mnie 10px na szerokości i 5px na wysokości). Dlaczego?

  2. Jeśli okno z obrazkiem jest mniejsze (większe) od samego obrazka to zaznaczając ramkę w TImage nie mamy rzeczywistych punktów z obrazka (kopiujemy inny fragment niż chcemy). Jeśli obrazek jest mniejszy niż obszar TImage to przeliczam to jak w poniższym kodzie. Nie wiem jednak co, jeśli obrazek jest większy od obszaru TImage (ten jest skalowany). Jak to obliczyć?

Ogólnie, czy ktoś z Was zajmował się takim przypadkiem? Mamy obraz w TImage i chcemy, żeby użytkownik zaznaczając myszą wybrał fragment obrazu (przyciął go) i przypisał do tego TImage. Chętnie dowiem się jak to zrobić dobrze, prosto i efektywnie.

Przykładowy kod - BARDZo niedoskonały i prymitywny.

type 
    Img_ImagePreview: TImage;
    CropFrame: TShape;

  private
    { Private declarations }
      CropMouseIsDown: Boolean;
      pStartPoint: TPoint;
      pStopPoint: TPoint;
  public
    { Public declarations }
  end;

...

procedure TImagePreview_Frm.FormShow(Sender: TObject);
var
   BMP : TBitmap;
begin
   BMP := TBitmap.Create;
   try
      BMP.Assign(Clipboard);

      Img_ImagePreview.Align := alClient;
      Img_ImagePreview.Visible := True;
      Img_ImagePreview.IncrementalDisplay := True;
      Img_ImagePreview.Proportional := True;
      Img_ImagePreview.Stretch := False;
      Img_ImagePreview.Transparent := True;
      Img_ImagePreview.AutoSize := True;
      Img_ImagePreview.Center := True;
      Img_ImagePreview.Picture := nil;
      Img_ImagePreview.Picture.Assign(BMP);
      Img_ImagePreview.Refresh;
   finally
      BMP.Free;
   end;
end;
   
procedure Img_ImagePreviewMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
   if (Button = mbLeft) then
      begin
         pStartPoint.X := X;
         pStartPoint.Y := Y;

         CropFrame.Shape := stRectangle;
         CropFrame.Brush.Color := clWhite;
         CropFrame.Brush.Style := bsClear;

         CropFrame.Pen.Mode := pmCopy;
         CropFrame.Pen.Color := clRed;
         CropFrame.Pen.Style := psDashDot;
         CropFrame.Pen.Width := 1;



         CropFrame.Width := 0;
         CropFrame.Height := 0;
         CropFrame.Left := pStartPoint.X + 10; // !?
         CropFrame.Top := pStartPoint.Y + 5; // !?

         CropFrame.BringToFront;
         CropFrame.Visible := True;


         CropMouseIsDown := True;
      end;
end;
procedure Img_ImagePreviewMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
   if (CropMouseIsDown = True) then
      begin
         pStopPoint.X := X;
         pStopPoint.Y := Y;
         CropFrame.Width := pStopPoint.X - CropFrame.Left;
         CropFrame.Height := pStopPoint.Y - CropFrame.Top;
      end;
end;
procedure Img_ImagePreviewMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
   BMP : TBitmap;
   BMP_Source : TBitmap;


   Img_W : Integer;
   Img_H : Integer;

   Real_W : Integer;
   Real_H : Integer;

   Diff_W : Integer;
   Diff_H : Integer;
begin
   if (CropMouseIsDown = True) then
      begin
         CropMouseIsDown := False;
         CropFrame.Visible := False;

         BMP_Source := TBitmap.Create;
         BMP := TBitmap.Create;
         try
            BMP_Source.Assign(Img_ImagePreview.Picture.Bitmap);

            Real_W := BMP_Source.Width;
            Real_H := BMP_Source.Height;

            Img_W := Img_ImagePreview.Width;
            Img_H := Img_ImagePreview.Height;


            Diff_W := Round((Img_W - Real_W) div 2);
            Diff_H := Round((Img_H - Real_H) div 2);


            BMP.SetSize(Abs(pStopPoint.X - pStartPoint.X), Abs(pStopPoint.Y - pStartPoint.Y));
            BMP.Canvas.CopyRect(Rect(0,0, BMP.Width, BMP.Height),
                                BMP_Source.Canvas,
                                Rect( pStartPoint.X-Diff_W, pStartPoint.Y-Diff_H, (pStopPoint.X-Diff_W)-10, (pStopPoint.Y-Diff_H)-5)
                               );

			
			// Copy to Clipboard (Cropped Image)
            Clipboard.Assign(BMP);
         finally
            BMP_Source.Free;
            BMP.Free;
         end;

         Close;
      end;
end;
0

wszystkie trzy Delphi sie na mnie obrazily i przestaly dzialac
wiec nie moge przeslac jakiegos przykladu :(

ad 1) ja bym dodal onMouseMove Canvas.Pixels[x,y]:= $AABBCC;
i obejrzał gdzie sie rysuje, moze masz zainstalowany jakis dziwny kursor myszki , na komputerze pociech widzialem taki dziwne cuda 😄
ad 2) Nie za bardzo rozumiem w czym jest problem , jak skalujesz obraz to mozesz obliczyc skale i sobie przeliczac z jednego do drugiego układu współrzędnych

1

Przykład z zaznaczaniem części obrazu (w PaintBox) udostępnił kiedyś @furious programming : https://4programmers.net/Forum/Delphi_Pascal/297943-pobieranie_image_area_i_color_area_pulpitu?p=1411371#id1411371
Natomiast jeżeli rozmiar obrazu jest inny niż rozmiar komponentu (TImage) i jest rozciągnięty to musisz obliczyć skalę tego rozciągnięcia, a następnie przeliczyć pozycję i rozmiar zaznaczenia wg tej skali.

0

@Marius.Maximus
Z Delphi trzeba jak z jajkiem... mnie najbardziej wpienia - doprowadza do szału - błąd debugera - po uruchomieniu programu - wszystko zwisa, koniec (pomaga tylko zabicie procesu)

Wierz mi, sprawdziłem jak to jest i jak napisałem mam przesunięcie o tych kilkanaście pikseli - być może trzeba jakoś obsłużyć kursor (ja po prostu sprawdziłem ile pikseli muszę przesunąć i je dodaje, dzięki czemu mam poprawne zaznaczenie - ale czy to jest normalne? Czy wartość tego przesunięcia jest zawsze taka sama? Nie wiem...

Problem ze skalowaniem jest taki, że jeśli bitmapa wczytana do TImage jest mniejsza niż TImage - nie jest ona skalowana (wyświetlana w oryginalnym rozmiarze) - mogę zatem obliczyć przesunięcie i zaznaczenie ramki jest prawidłowe. ALE - jeśli bitmapa w TImage jest większa niż obszar TImage, to jest ona automatycznie skalowana, żeby się zmieściła - nie wiem jak to przeliczyć.

@Paweł Dmitruk
Kurczę, nie znalazłem wcześniej tego przykładu od @furious programming (wygląda interesująco i z pewnością działa dobrze). Sprawdzę jego przykład. Mówisz, że swoje rozwiązanie oparł na TPaintBox - preferuję TImage, ale to można pokombinować. Ze skalowaniem jest jak pisałem powyżej... spróbuję zgłębić temat ponownie, musi być sposób na przeliczenie skali.

1

To co kiedyś zmontowałem, miało być prostym narzędziem do zaznaczania — wirtualną ramką, renderowaną na jakimkolwiek płótnie i obsługującą standardowe zdarzenia myszy do jej przesuwania i rozciągania. Ten programik później jeszcze kilka razy modyfikowałem, bo udostępniałem jego źródła również na forum Lazarusa (dodałem np. renderowanie tekstu wewnątrz ramki).

Taką ramkę do przycinania obrazu można zaimplementować na wiele sposobów. To co sam zrobiłem jest ramką wirtualną, a to oznacza, że informacje o niej są przechowywane w osobnym obiekcie, nie powiązanym z żadnym komponentem. Można jej używać na dowolnym płótnie i modyfikować za pomocą standardowych zdarzeń, a więc nie tylko w TPaintBox, ale też w TImage, TPanel czy nawet bezpośrednio w TForm. Choć najlepiej by było, aby klasa komponentu z płótnem, na którym renderowana ma być ramka, mogła obsługiwać fokus, a więc i przechwytywać wciskane klawisze.


Taką ramkę do cropu obrazu możesz zaimplementować wirtualnie (tak jak ja to zrobiłem), ale też możesz ją stworzyć jako komponent wizualny. Wtedy wystarczy taki komponent położyć na formie (nad obrazem do przycięcia) i ten komponent po prostu przesuwać i rozciągać. Problem jednak w tym, że uzyskanie półprzezroczystości kontrolki wymaga obsługi komunikatu WM_ERASEBKGND i kopiowania tła do pomocniczej bitmapy, po to aby później je wyrenderować np. przyciemnione, rozmyte itp.

Przykłady dla Delphi powinieneś znaleźć bez problemu w sieci, również na 4p, np. w tym wątku — https://4programmers.net/Forum/Delphi_Pascal/280892-przezroczysty_komponent_zamazujacy_tlo?p=1313823#id1313823. Co prawda w tym wątku chodzi o blur formularza, ale efekt możesz sobie dostosować jaki tylko chcesz, albo i po prostu tylko renderować ramkę bez żadnych efektów.

0

@furious programming
Ja poszedłem na łatwiznę i użyłem jako ramki TShape - też można, ale oznacza drogę na skróty. A przecież można to zrobić ładniej i lepiej (jak ty w tym przykładzie).
Sprawdzę twoje rozwiązanie z TImage - ja potrzebuję właśnie prostej ramki, bez cudów - bo to funkcjonalność poboczna, acz przydatna.
Większym problemem dla mnie jest określenie "skali" obrazu w TImage (gdy wczytany obraz jest większy niż obszar Timage). Bo, jeśli będę miał "ramkę" na TImage (użytkownik może dowolnie zmieniać jej rozmiar, przesuwać) to wciąż muszę pobrać obszar zaznaczenia (punkt początkowy i końcowy ramki) żeby skopiować fragment bitmapy jako nowa, ta przycięta - a bez prawidłowego określenia skali, skopiuje nieprawidłowy fragment bitmapy).

0

@Pepe: jeśli potrzebujesz inspiracji, to pobaw się systemowym narzędziem o nazwie Snip & Sketch (Windows 10+). Służy do robienia i obróbki zrzutów ekranu i posiada narzędzie do przycinania, które dość fajnie działa w przypadku, gdy obraz jest większy niż klient okna tego programu. Również całkiem wygodnie aktualizuje pozycję obrazu podczas przesuwania zaznaczenia, gdy obraz nie mieści się w kliencie.

1

Wycinając kawałek musisz sprawdzić czy jest włączone rozciąganie i czy zachowane są proporcje obrazu, mniej-więcej coś takiego:

procedure TForm1.Button1Click(Sender: TObject);
var
  scale_w, scale_h: Double;
  bmp: TBitmap;
  rect: TRect;
begin
  //Image1 - TImage z obrazkiem
  //selrect - TRect przechowujący obszar zaznaczenia
  scale_w := 1;
  scale_h := 1;
  if Image1.Stretch then //jeżeli rozciąganie
  begin
    scale_w := Image1.Width / Image1.Picture.Bitmap.Width;     //skala = rozmiar kontrolki / rozmiar obrazka
    scale_h := Image1.Height / Image1.Picture.Bitmap.Height;
    if Image1.Proportional then     //jeżeli zachowane proporcje
    begin
      if scale_w < scale_h then     //to skala jest jedna, mniejsza z tych dwóch
        scale_h := scale_w
      else
        scale_w := scale_h;
    end;
  end;
  rect.Left := trunc(selrect.Left / scale_w);      //przelicz obszar zaznaczenia wg skali
  rect.Top := trunc(selrect.Top / scale_h);
  rect.Width := trunc(selrect.Width / scale_w);
  rect.Height := trunc(selrect.Height / scale_h);
  bmp := TBitmap.Create;
  bmp.SetSize(rect.Width, rect.Height);
  bmp.Canvas.CopyRect(bmp.Canvas.ClipRect, Image1.Picture.Bitmap.Canvas, rect);    //skopiuj wybraną część obszru źródłowego
  bmp.SaveToFile('wycinek.bmp');
  bmp.Free;
end;
0

@Paweł Dmitruk
Dzięki. Sprawdzę to, ale na to jeszcze czas... wpierw trzeba zrobić ramkę zaznaczenia...

@furious programming
Jak pisałem, moja aplikacja korzysta z TImage do przechowywania obrazu. Ten wczytywany jest bezpośrednio ze schowka.
Obraz jest centrowany w kontrolce (dlatego muszę przeliczyć jego rzeczywisty rozmiar).

Użyłem twojego demo ramki zaznaczenia (link)
Twój przykład działa, gdy obraz (bitmapa) rysowana jest na kanvie TPaintBox.
Próbowałem przerobić go w taki sposób, by obraz wczytany był do TImage (TImage nie posiada metody OnPaint), ale twoja ramka rysowana na kanvie TImage. Mówiąc oględnie - nie działa to!
Co robię źle? Prawdę mówiąc zakręciłem się...

Oto przykładowy kod (trzeba wcisnąć Print Screen żeby wczytać obrazek ze schowka - nasz pulpit, lub Alt+print Screen aktywnego okna).
Crop Test.zip

-Pawel

2

@Pepe - w załączniku przerobiony przykład @furious programming - zamieniłem TPaintBox na TImage oraz dodałem wycinanie. Przykład w Lazarus-ie ale nie będzie problemem przerucić na Delphi
screenshot-20240620134459.png
frame.zip

0

Zainstalowałem Lazarusa...
Jeszcze tylko tryb ciemny interfejsu i będzie git! 😛

2

@Pepe pomimo że nie na temat wątku, ale przerzucę do posta ponieważ w komentarzu się nie zmieścici.
Ja akurat korzystam z jasnego motywu z pływającymi oknami.

Predefiniowane ustawienia edytora kodu znajdziesz m.in. tutaj:

Pobierasz plik xml z definicją kolorów i wklejasz do katalogu z konfiguracją lazarusa do podkatalogu userschemes (jeżeli nie ma to musisz utworzyć) - domyślnie jest to %localappdata%\lazarus. Po restarcie IDE nowe "skórki" zostaną wczytane. Aby zmienić wchodzisz: Narzędzia > Opcje a nastepnie:
screenshot-20240621103639.png

Jeżeli chcesz przechowywać konfigurację w innej ścieżce to w głównym katalogu z lazarusem musisz utworzyć plik lazarus.cfg z zawartością: --primary-config-path=C:\lazarus\.config (oczywiście wstawiasz swoją ścieżkę)
UWAGA: jeżeli zmieniasz po uruchomieniu/konfiguracji/instalacji komponentów to wszystko będziesz musiał robić od początku

Co do połączenia wszystkiego w jedno okno i zadokowania edytora formularzy to musisz zainstalować komponenty (anchordocking, anchordockingdsgn, dockedformeditor):
screenshot-20240621103741.png
screenshot-20240621104120.png
Po przebudowaniu IDE wygląda tak:

  • edytor kodu:
    screenshot-20240621104612.png
  • edytor formatki
    screenshot-20240621105152.png

Możesz jeszcze w opcjach włączyć Ouutline (global) wtedy każda para begin ... end będzie wyróżniona innym kolorem:
screenshot-20240621104828.png

domyślnie jest zdefiniowanych 6 poziomów, ale można 10 oraz można ustawić kolory wg własnego gustu:
screenshot-20240621105009.png

1

@Pepe: jeśli chodzi o kolory edytora kodu dopasowane do ciemnych okien, to polecam raz sobie je ustawić w ustawieniach IDE, a kiedy już dojdziesz do ostatecznej formy, to po prostu wyeksportować sobie schemat do xml, aby mieć kopię zapasową. Taki plik potem możesz w dowolnym momencie skopiować do katalogu tej samej lub kolejnej instalacji IDE, aby wszędzie mieć tak samo wyglądający edytor kodu.

screenshot-20240624121613.png

BTW: ponarzekam — fajnie, że jest przycisk Export, ale o przycisku Import to już nikt nie pomyślał. Standard.


Proszę o jeszcze jeden tip... jak zbudować aplikację z ciemnym motywem. Czy to tak proste jak z IDE Lazarusa? Czy wystarczy dodać jakiś unit? Czy łatwo zmienić Ciemny Motyw na jasny (w czasie działania aplikacji)? Czy to w ogóle możliwe?

W repozytorium pakietu metadarkstyle jest katalog examples — pobaw się nimi. Z tego co mi wiadomo, aby apka wystartowała jako ciemna, wystarczy raptem kilka linijek kodu dorzucić do projektu (najlepiej w pliku .lpr). Natomiast zmiana schematu at runtime też jest możliwa i też wymaga kilku linijek kodu.

Pamiętaj też, że pakiet ten pozwala na samodzielne określenie kolorystyki — kształt/styl dekoracji okien będzie zgodny z systemowym, ale kolorki możesz samodzielnie ustawić. Ale tym się jeszcze nie bawiłem, bo używam tego pakietu tylko do kolorowania samego Lazarusa. Swoje edytory robiłem w natywnym stylu, bo taki mi bardziej do nich pasował.

0
Paweł Dmitruk napisał(a):

@Pepe - w załączniku przerobiony przykład @furious programming - zamieniłem TPaintBox na TImage oraz dodałem wycinanie. Przykład w Lazarus-ie ale nie będzie problemem przerucić na Delphi
screenshot-20240620134459.png
frame.zip

@Paweł Dmitruk
@furious programming
Minęło trochę czasu, chciałem to zaimplementować - a tu klops!

Komponent TImage z Delphi nie posiada zdarzenia "onPaint"!
I co teraz? Ramka zaznaczenia się nie rysuje, bo nie ma zdarzenia odświeżającego płótno obrazka...

Proszę o pomoc. Czy można dodać OnPaint do standardowego komponentu TImage (w sensie dodać tę jedną właściwość poprzez utworzenie klasy bazującej na TImage ale z obsługą onPaint) - jak to się robi?

Ps: Idąc na skróty, można położyć komponent TPaintBox na TImage, dzięki czemu wszystko działa jak należy - ale jest jeden problem - zmiana rozmiaru ramki (na TPaintBox) powoduje odmalowywanie obrazka w TImage (migotanie). Wyłączenie automatycznego wyrównania (Align=alClient) poprawia sytuację... Ale to nieco "brzydkie" rozwiązanie...

-Pawel

3

Zdarzenie zawsze można dodać.
Wersja krótka tylko obsługa komunikatu:

type
  TImage = class(Vcl.ExtCtrls.TImage)
  protected
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  end;

//....

procedure TImage.WMPaint(var Message: TWMPaint);
begin
  inherited;//tu coś robisz w przykładzie wywołana domyślna obsługa komunikatu
end;

A tu dodane zdarzenie i przykład użycia:

type
  TImage = class(Vcl.ExtCtrls.TImage)
  private
    fOnPaint: TNotifyEvent;
  protected
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  published
    property OnPaint: TNotifyEvent read fOnPaint write fOnPaint;
  end;

  TForm1 = class(TForm)
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }

    procedure Image1OnPaint(Sender: TObject);
  public
    { Public declarations }

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TImage.WMPaint(var Message: TWMPaint);
begin
  if Assigned(fOnPaint) then
    fOnPaint(Self)
  else
    inherited;
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
  Image1.OnPaint:= Image1OnPaint;
end;

procedure TForm1.Image1OnPaint(Sender: TObject);
begin
  //Tutaj
end;

0

@kAzek
OK, dodanie zdarzenia onPaint do TImage działa (to jest ramka jest rysowana - ale obrazek w TImage nie, a sama ramka jest defacto niewidoczna).

1

TImage służy przede wszystkim do wyświetlania załadowanych do niego obrazów i posiada logikę odpowiedzialną za to, a ta może kolidować z ręcznym renderowaniem zawartości tej kontrolki. Dlatego też doimplementowanie zdarzenia OnPaint to tylko wierzchołek góry lodowej tego, co należy zrobić.

I dlatego właśnie korzystam z TPaintBox, aby móc wygodnie renderować to czego potrzebuję i ten komponent mi w tym nie przeszkadza.

0

@Pepe Spróbuj coś w tym stylu (nawiązując do mojego poprzedniego kodu):

Albo zmienić wywołanie dodanego zdarzenia (wtedy zawsze wykona się domyśle rysowanie):

procedure TImage.WMPaint(var Message: TWMPaint);
begin
  inherited; //wymuś domyślną obsługę komunikatu
  if Assigned(fOnPaint) then
    fOnPaint(Self)
end;

Albo można też tak:

procedure TForm1.Image1OnPaint(Sender: TObject);
var
  img: TImage;
begin
  img:= TImage(Sender);
  img.Paint; //to powinno wymusić rysowanie obrazka
  //tu sobie rysujesz
  img.Canvas.Brush.Style:= bsClear;
  img.Canvas.Rectangle(0,0, 100, 100); //zmiast tego
end;
0

A próbowałeś podejść do tematu od całkiem innej strony?
Manipulacja obrazami w Delphi nie należy do najprzyjemniejszych a w JavaScript i przeglądarce jest całkiem sympatyczna.
Nie myślałeś o tym żeby osadzić w swojej apl9ikacje TEdgeBrowser czy inny TWebBrowser i w nim odpalić np.: https://www.codehim.com/demo/crop-image-in-canvas-using-javascript/
Myślę, że na dłuższą metę to może być nawet bezpieczniejsze i bardziej rozwojowe rozwiązanie bo:

  • więcej formatów się obsłuży,
  • samo z siebie będzie podążać za nowymi formatami,
  • można wykorzystać w innym programie.

W takim TWebBrowser z poziomu Delphi = parenta możesz wywołać dowolny Javascript, możesz przechwycić zdarzenia, zwrócić do Delphi wartość wywołanej w JS funkcji. Wg mnie warto to rozważyć bo nakład pracy niewielki a możliwości spore.

0

Jeszcze raz proszę o pomoc...
Jest problem z wycięciem odpowiedniego fragmentu z obrazu źródłowego (pod ramką).

Chcę, by obrazek źródłowy wczytywany był do TImage, który ma następujące właściwości:

   Img_CropImage_Source.Align := alClient;
   Img_CropImage_Source.AlignWithMargins := False;
   Img_CropImage_Source.IncrementalDisplay := True;
   Img_CropImage_Source.Proportional := True;
   Img_CropImage_Source.Stretch := True;
   Img_CropImage_Source.Transparent := False;
   Img_CropImage_Source.AutoSize := True;
   Img_CropImage_Source.Center := True;

W procedurze cropowania mamy:

procedure TCropImage_Frm.Button2Click(Sender: TObject);
var
  scale_w, scale_h: Double;
  bmp: TBitmap;
  rect, selrect: TRect;
  ms: TMemoryStream;
begin
  //Image1 - TImage z obrazkiem
  //selrect - TRect przechowujący obszar zaznaczenia
  selrect := FSelectionTool.Area;
  scale_w := 1;
  scale_h := 1;
  if Image1.Stretch then //jeżeli rozciąganie
  begin
    scale_w := Image1.Width / Image1.Picture.Bitmap.Width;     //skala = rozmiar kontrolki / rozmiar obrazka
    scale_h := Image1.Height / Image1.Picture.Bitmap.Height;
    if Image1.Proportional then     //jeżeli zachowane proporcje
    begin
      if scale_w < scale_h then     //to skala jest jedna, mniejsza z tych dwóch
        scale_h := scale_w
      else
        scale_w := scale_h;
    end;
  end;
  rect.Left := trunc(selrect.Left / scale_w);      //przelicz obszar zaznaczenia wg skali
  rect.Top := trunc(selrect.Top / scale_h);
  rect.Width := trunc(selrect.Width / scale_w);
  rect.Height := trunc(selrect.Height / scale_h);
  bmp := TBitmap.Create;
  bmp.SetSize(rect.Width, rect.Height);
  bmp.Canvas.CopyRect(bmp.Canvas.ClipRect, Image1.Picture.Bitmap.Canvas, rect);    //skopiuj wybraną część obszru źródłowego
  ms := TMemoryStream.Create;
  bmp.SaveToStream(ms);
  bmp.Free;
  ms.Position := 0;
  Image2.Picture.LoadFromStream(ms);
  ms.Free;
end;

Jak widać, powyższy kod uwzględnia automatyczne Rozciąganie (Stretch) oraz Proporcje (Proportional). Przeliczana skala obrazka jest OK.

ALE - obrazek nie może być wycentrowany - a tego właśnie potrzebuję. Jeśli ustawię właściwość Center := True, to niestety to nie zadziała - pobrany zostanie fragment obrazka, ale nieprawidłowy region. Macie pomysł jak uwzględnić wycentrowany obrazek (przy zachowaniu align=alClient, Stretch/Proportional=True)?

@Paweł Dmitruk
@furious programming
Bardzo proszę o pomoc.
Dziękuję!!!

Edit: Kombinowałem, kombinowałem i prawie wykombinowałem... jeden wielki hack, ale prawie działa :P

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.