oryginalne są pliki BMP które załączyłem w zip-ie, wszystkie PNG to już konwersja, bo nie mogłem tu wsadzić gołego BMP w tekście forum.
Zawsze możesz je spakować do .zip
i dorzucić do załączników.
tak, rogi są zawsze takiego samego koloru -ale nie mogę chyba zrobić zamiany tego koloru, bo na części roboczej wykresu też czasem taki kolor się trafia..
Możesz i napisałem Ci w poprzedniej wiadomości jak to zrobić. Nie chodzi o to, aby pobrać kolor rogów i w pętli pozamieniać wszystkie piksele w grafice o takim kolorze na inny - w ten sposób wykasujesz również kolor z wnętrza mapy. Trzeba to zrobić w taki sam sposób, w jakim wykonywane jest wypełnianie danym kolorem w programach graficznych (np. w systemowym Paint), czyli skorzystać z algorytmu rozrostu ziarna. Na szczęście nie trzeba go implementować samemu, bo w klasie TCanvas
już taki istnieje - to właśnie zadanie metody FloodFill
.
- ta zamiana, czy konwersja typu TColor do TRGBTriple, żebym mógł dla wybranych pixeli określić ich składowe RGB (jeden narożnik i jakiś pixel z brzegu tła)
Możesz to zrobić ręcznie za pomocą operatorów koniunkcji (and) i przesunięcia bitowego (shr):
R := AColor and $0000FF;
G := AColor and $00FF00 shr 8;
B := AColor and $FF0000 shr 16;
Gdzie AColor
to zmienna typu TColor
. Można też skorzystać z gotowej procedury do wyodrębniania tych składowych. Nie wiem jak to wygląda w Delphi, ale w Lazarusie jest do tego procedura Graphics.RedGreenBlue
i robi dokładnie to samo. A w drugą stronę, czyli zbudowanie wartości typu TColor
z trzech składowych, można zrealizować znów za pomocą operatorów:
AColor := (B shl 16) or (G shl 8) or R;
lub użyć do tego funkcji Graphics.RGBToColor
, która robi to samo.
- w jaki sposób użyć zaznaczenia myszą, żeby określić obszar aktywny, taki Area_IN i Area_OUT
Zależy w czym sobie wyświetlasz tę bitmapkę i również zależy od tego, jaki efekt Cię interesuje. Czy ramka zaznaczania ma być widoczna podczas samego zaznaczania, jak i po jego zakończeniu (jak np. w Paint), czy ma być niewidoczna. Zasadniczo sprowadza się to do obsługi kilku zdarzeń:
-
OnMouseDown
- wciśnięcie przycisku myszy, oprogramowanie rozpoczęcia zaznaczania,
-
OnMouseMove
- przesuwanie kursora, oprogramowanie dynamicznego rysowania ramki zaznaczenia,
-
OnMouseUp
- zwolnienie przycisku myszy, oprogramowanie zakończenia zaznaczania.
Pamiętaj, że jeśli chcesz dynamicznie malować taką ramkę to obraz bitmapy musi sobie siedzieć w jakiejś zmiennej, a w komponencie trzeba wyświetlić najpierw bitmapkę ze zmiennej, a następnie domalować na niej ramkę. Ramkę należy malować wyłącznie na płótnie komponentu, tak aby nie zamazać właściwego obrazu.
Napisałem taką prostą aplikację do wizualnego zaznaczania obszaru na komponencie typu TPaintBox
. Bitmapa z pliku siedzi w oryginale w polu klasy, wyświetlana jest w komponencie, a dodatkowo, podczas zaznaczania i po zaznaczeniu widoczna jest ramka zaznaczenia (tylko w komponencie, na bitmapie jej nie ma, bo być jej nie może).
Działa to w ten sposób, że zaznaczanie realizowane jest przez obsługę lewego przycisku myszy (prawy nie jest obsługiwany - dla niego można dorobić inną opcję, np. wyświetlenie menu kontekstowego). Podczas zaznaczania rysowana jest kropkowana ramka w kolorze jasnoszarym, a po zwolnieniu przycisku i tym samym zakończeniu zaznaczania, rysowana jest ramka w kolorze białym. Kliknięcie na komponencie to usunięcie ramki.
Po kolei:
type
TSelectingMode = (smNone, smSelecting, smSelected);
Typ określający tryb zaznaczania - smNone
to brak zaznaczania (nic się nie dzieje, ramka nie jest zaznaczona), smSelecting
oznacza trwanie zaznaczania, smSelected
oznacza stan po zakończeniu zaznaczania.
private
FHeatMap: TBitmap;
FSelectingMode: TSelectingMode;
FSelectedRect: TRect;
FHeatMap
to bitmapa z oryginalnym obrazem załadowanym z pliku, FSelectingMode
przechowuje bieżący tryb zaznaczania, a FSelectedRect
przechowuje obszar zaznaczenia - z niego będziesz mógł skorzystać po zaznaczeniu, np. w celu skopiowania fragmentu obrazu do osobnej bitmapy.
operator = (const ALeft, ARight: TPoint) Return: Boolean;
begin
Result := (ALeft.X = ARight.X) and (ALeft.Y = ARight.Y);
end;
W Lazarusie 1.6.2 typ TPoint
nie ma zdefiniowanego zachowania dla tego operatora, więc przeciążyłem go sobie. Chyba że funkcja ta zdefiniowana jest w module Types
, ale tego nie dołączałem, bo nie było takiej potrzeby. Nie pamiętam czego używasz, ale być może nie trzeba tego robić.
I kod klasy formularza:
procedure TMainForm.FormCreate(ASender: TObject);
begin
FHeatMap := TBitmap.Create();
FHeatMap.LoadFromFile('map.bmp');
FSelectingMode := smNone;
end;
Utworzenie bitmapy w pamięci, załadowanie jej z pliku oraz ustawienie trybu zaznaczenia na... brak zaznaczenia.
procedure TMainForm.FormDestroy(ASender: TObject);
begin
FHeatMap.Free();
end;
Zwolnienie obiektu bitmapy z pamięci podczas usuwania z pamięci obiektu formularza.
procedure TMainForm.CPaintBoxPaint(ASender: TObject);
var
LBox: TPaintBox absolute ASender;
begin
LBox.Canvas.Draw(0, 0, FHeatMap);
if FSelectingMode <> smNone then
with LBox.Canvas do
begin
case FSelectingMode of
smSelecting: Pen.Color := clLtGray;
smSelected: Pen.Color := clWhite;
end;
Pen.Style := psDot;
Brush.Style := bsClear;
Rectangle(FSelectedRect);
end;
end;
Zdarzenie malowania komponentu. Najpierw malowana jest bitmapa na całej powierzchni kontrolki. Następnie sprawdzane jest, czy użytkownik zaznacza lub zaznaczył obszar i jeśli tak, to:
- ustawiany jest kolor "ołówka" - jeśli użytkownik zaznacza obszar to ustawiany jest kolor jasnoszary, a jeśli już zaznaczył to kolor biały,
- ustawienie stylu ołówka na punktowy (ramka będzie kropkowana) oraz stylu wypełnienia na pusty (wnętrze obszaru nie zostanie zamalowane),
- wypełnienie obszaru na podstawie ustawień płótna (ołówka i wypełnienia).
procedure TMainForm.CPaintBoxMouseDown(ASender: TObject; AButton: TMouseButton; AShift: TShiftState; AX, AY: Integer);
var
LBox: TPaintBox absolute ASender;
begin
if AButton = mbLeft then
begin
FSelectingMode := smSelecting;
FSelectedRect := Rect(AX, AY, AX, AY);
LBox.Invalidate();
end;
end;
Obsługa wciśnięcia przycisku myszy. Najpierw sprawdzane jest czy użytkownik wcisnął LPM i jeśli tak, następuje:
- ustawienie trybu na
smSelecting
, czyli trwanie zaznaczania,
- ustawienie danych obszaru zaznaczenia - w tym miejscu lewy górny róg obszaru jest taki sam jak prawy dolny róg tego obszaru,
- przemalowanie komponentu.
procedure TMainForm.CPaintBoxMouseMove(ASender: TObject; AShift: TShiftState; AX, AY: Integer);
var
LBox: TPaintBox absolute ASender;
begin
if FSelectingMode = smSelecting then
begin
FSelectedRect.BottomRight := Point(AX, AY);
LBox.Invalidate();
end;
end;
Obsługa ruchu kursora nad komponentem. Obsługa aktywna jest tylko i wyłącznie w przypadku, gdy właśnie trwa zaznaczanie, więc musi być ustawiony tryb smSelecting
- w innym przypadku nic nie powinno być wykonywane. Jeśli trwa zaznaczanie to aktualizujemy prawy dolny róg obszaru na podstawie parametrów AX
i AY
, a także przemalowujemy komponent, aby ramka zaznaczenia była na bieżąco aktualizowana na ekranie.
procedure TMainForm.CPaintBoxMouseUp(ASender: TObject; AButton: TMouseButton; AShift: TShiftState; AX, AY: Integer);
var
LBox: TPaintBox absolute ASender;
begin
if AButton = mbLeft then
begin
if FSelectedRect.TopLeft = FSelectedRect.BottomRight then
FSelectingMode := smNone
else
FSelectingMode := smSelected;
LBox.Invalidate();
end;
end;
Obsługa zwolnienia przycisku myszy. Jeśli użytkownik zwolnił lewy przycisk myszy to sprawdzamy, czy cokolwiek zaznaczył. Jeśli tylko kliknął (czyli nic nie zaznaczył) to ustawiamy tryb na smNone
(brak zaznaczenia), a jeśli coś zaznaczył to ustawiamy tryb na smSelected
. Na koniec odmalowujemy komponent, aby ramka została namalowana w białym kolorze.
Podczas zaznaczania będziesz widział coś takiego (kursora nie widać na zrzutach - jest obok prawego dolnego rogu ramki):
A po zaznaczeniu coś takiego:
Do malowania zaznaczenia można też użyć metody TCanvas.DrawFocusRect
- wtedy ramka będzie malowana z kropek, a kolor każdego piksela będzie odwrotny do koloru tła. Czyli zwykły xor
, otrzymany w gratisie.
To tyle - jak widać obsługa zaznaczania obszaru nie jest wcale taka trudna. To co podałem wyżej to tylko podstawy zaznaczania - ten mechanizm można wzbogacić o inne opcje oraz jakieś fajne bajery. W każdym razie, w załączniku podaję pełne źródła napisane w Lazarusie (Delphi nie mam, nie używam), więc możesz sobie zobaczyć jak to wygląda w całości. Dodaję też plik wykonywalny, abyś mógł sprawdzić działanie bez kompilacji projektu.
Have fun :]