How to clip a bitmap?

Kompletny Przewodnik po Bitmapach w Androidzie

20/04/2025

Rating: 4.47 (7086 votes)

W świecie tworzenia aplikacji mobilnych dla systemu Android, obrazy są wszechobecne i kluczowe dla wizualnego doświadczenia użytkownika. Sercem zarządzania obrazami w Androidzie jest klasa Bitmap. Jest to fundamentalna struktura danych, która reprezentuje obraz rastrowy, czyli siatkę pikseli. Zrozumienie, jak tworzyć, manipulować i efektywnie zarządzać obiektami Bitmap, jest niezbędne dla każdego dewelopera Androida. W tym artykule zagłębimy się w różne aspekty pracy z bitmapami, od ich podstawowego tworzenia, przez zaawansowane techniki przycinania (cropping) i maskowania (clipping), aż po optymalizację wydajności i zarządzania pamięcią.

How do I make a new bitmap?
Write a compressed version of the bitmap to the specified outputstream. Tries to make a new bitmap based on the dimensions of this bitmap, setting the new bitmap's config to the one specified, and then copying this bitmap's pixels into the new bitmap. void

Bitmapa w Androidzie może być zarówno zmienna (mutable), co oznacza, że jej piksele mogą być modyfikowane po utworzeniu, jak i niezmienna (immutable), co uniemożliwia ich późniejszą edycję. Wybór odpowiedniego typu bitmapy ma kluczowe znaczenie dla wydajności i stabilności aplikacji. Niezmienne bitmapy są często bezpieczniejsze do współdzielenia między wątkami i mogą być optymalizowane przez system, podczas gdy zmienne bitmapy są niezbędne, gdy potrzebujemy rysować bezpośrednio na obrazie lub modyfikować jego zawartość.

Tworzenie Nowych Obiektów Bitmap

Klasa Bitmap udostępnia wiele statycznych metod createBitmap(), które pozwalają na elastyczne tworzenie nowych obiektów bitmapy w zależności od potrzeb. Każda z tych metod przyjmuje różne parametry, co pozwala na dostosowanie procesu tworzenia do konkretnego scenariusza, takiego jak tworzenie pustej bitmapy, kopiowanie istniejącej, czy tworzenie jej z tablicy pikseli.

Tworzenie Pustej Bitmapy

Najprostszą formą tworzenia bitmapy jest określenie jej wymiarów (szerokości i wysokości) oraz konfiguracji pikseli. Konfiguracja (Bitmap.Config) określa, w jaki sposób piksele są przechowywane w pamięci, wpływając na jakość obrazu, przezroczystość i zużycie pamięci.

public static Bitmap createBitmap (int width, int height, Bitmap.Config config)

Ta metoda tworzy zmienną bitmapę o określonej szerokości i wysokości. Domyślna gęstość bitmapy jest zgodna z gęstością wyświetlacza. Przykładowo, Bitmap.Config.ARGB_8888 jest często używany, ponieważ zapewnia pełną przezroczystość (kanał alfa) i wysoką jakość kolorów, ale zużywa więcej pamięci niż np. RGB_565, który nie obsługuje kanału alfa. Jeśli wybierzesz Config.HARDWARE, bitmapa będzie zawsze niezmienna.

Tworzenie Bitmapy z Tablicy Pikseli

Możesz również utworzyć bitmapę z tablicy wartości całkowitych (int[] colors), gdzie każda wartość reprezentuje kolor piksela. Jest to przydatne, gdy masz już dane pikseli w pamięci.

public static Bitmap createBitmap (int[] colors, int width, int height, Bitmap.Config config)

Ta metoda tworzy niezmienną bitmapę. Tablica colors powinna być na tyle duża, aby pomieścić wszystkie piksele (width * height). Jeśli wybrana konfiguracja nie obsługuje kanału alfa (np. RGB_565), bajty alfa z tablicy colors zostaną zignorowane.

Kopiowanie i Podzbiory Istniejących Bitmap

Często potrzebujemy stworzyć nową bitmapę na podstawie istniejącej, na przykład kopiując ją w całości lub wycinając jej fragment. Metody te pozwalają na efektywne manipulowanie obrazami bez konieczności ponownego ładowania danych.

How do I cut a bitmap with a topcutoff?
topcutoff is what you want to cut of on top of the image and buttomcutoff on the buttom (if needed) height = height - topcutoff; height = height - bottomcutoff; croppedBitmap = Bitmap.createBitmap (croppedBitmap, 0, topcutoff, width, height); Basically you just set a startpoint (topcutoff) from where to begin displaying the bitmap.
public Bitmap copy (Bitmap.Config config, boolean isMutable)

Metoda copy() tworzy nową bitmapę, kopiując piksele z oryginalnej. Możesz określić nową konfigurację i to, czy nowa bitmapa ma być zmienna. Jest to użyteczne, gdy chcesz zmienić konfigurację pikseli (np. zmniejszyć zużycie pamięci) lub potrzebujesz zmiennej wersji niezmiennej bitmapy.

public static Bitmap createBitmap (Bitmap source, int x, int y, int width, int height)

Ta metoda jest kluczowa do przycinania obrazów. Zwraca bitmapę będącą podzbiorem źródłowej bitmapy, zaczynając od współrzędnych (x, y) i mającą określoną width i height. Nowa bitmapa może być tym samym obiektem co źródło (jeśli jest niezmienna i wycinasz całość) lub jego kopią. Ma tę samą gęstość i przestrzeń barw co oryginał.

public static Bitmap createBitmap (Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter)

Ta zaawansowana wersja pozwala na zastosowanie opcjonalnej macierzy transformacji (Matrix) oraz filtrowania (filter) podczas tworzenia podzbioru. Jest to przydatne do skalowania, obracania lub przekształcania wyciętego fragmentu.

Skalowanie Bitmapy

Jeśli potrzebujesz zmienić rozmiar bitmapy, createScaledBitmap() jest odpowiednią metodą:

public static Bitmap createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)

Tworzy nową bitmapę, skalując istniejącą. Parametr filter decyduje, czy użyć filtrowania dwuliniowego (true) dla lepszej jakości obrazu kosztem wydajności, czy skalowania najbliższego sąsiada (false) dla szybkości.

Porównanie Metod Tworzenia Bitmap

MetodaCelZmiennośćZastosowanie
createBitmap(width, height, config)Pusta bitmapaZmiennaRysowanie od zera, tworzenie buforów
createBitmap(colors, width, height, config)Z tablicy pikseliNiezmiennaŁadowanie danych pikseli
copy(config, isMutable)Kopia istniejącejZależna od isMutableZmiana konfiguracji, uzyskanie zmiennej wersji
createBitmap(source, x, y, w, h)Przycięcie/podzbiórZwykle niezmienna (może być zmienna, jeśli źródło jest)Wycinanie fragmentów obrazu
createScaledBitmap(src, dw, dh, filter)SkalowanieNiezmiennaZmiana rozmiaru obrazu

Przycinanie (Cropping) Obrazów Bitmap

Przycinanie, czyli cropping, to proces wyodrębniania prostokątnego fragmentu z większego obrazu. Jest to jedna z najczęstszych operacji na bitmapach. Jak wspomniano, statyczna metoda Bitmap.createBitmap(source, x, y, width, height) jest idealnym narzędziem do tego celu. Parametry x i y określają współrzędne lewego górnego rogu obszaru, który chcemy wyciąć, a width i height definiują jego rozmiar.

Przycinanie Dolnej Części Bitmapy

Rozważmy scenariusz, w którym chcemy wyciąć tylko dolne 20% obrazu, zaczynając od lewej krawędzi. Oryginalna bitmapa została uzyskana z kamery, więc jej wymiary mogą być różne. Kluczem jest dynamiczne obliczenie współrzędnej y, od której zaczniemy wycinanie.

Jeśli chcemy dolne 20% obrazu, oznacza to, że musimy zacząć wycinanie na 80% wysokości od góry. Współrzędna y dla lewego górnego rogu wycinanego fragmentu będzie wynosić oryginalna_wysokość * 0.8. Wysokość wycinanego fragmentu będzie oczywiście oryginalna_wysokość * 0.2.

Oto zmodyfikowany kod do osiągnięcia tego celu:

// Zakładamy, że 'toBeCropped' to Twoja oryginalna bitmapa // final Bitmap toBeCropped = BitmapFactory.decodeFile(mFile.getPath()); int originalHeight = toBeCropped.getHeight(); int originalWidth = toBeCropped.getWidth(); // Obliczamy współrzędną Y, od której ma się zacząć wycinanie // Chcemy dolne 20%, więc zaczynamy na 80% wysokości od góry int startY = (int) (originalHeight * 0.8); // Wysokość wycinanego fragmentu to 20% oryginalnej wysokości int cropHeight = (int) (originalHeight * 0.2); // Upewniamy się, że nie wyjdziemy poza granice bitmapy // Jeśli cropHeight jest 0 (np. dla bardzo małych obrazów), ustawiamy na 1, aby uniknąć błędu if (cropHeight <= 0) { cropHeight = 1; } // Jeśli startY + cropHeight przekracza oryginalną wysokość, dostosowujemy cropHeight if (startY + cropHeight > originalHeight) { cropHeight = originalHeight - startY; } // Tworzymy nową bitmapę z wyciętym fragmentem // Zaczynamy od x=0 (lewa krawędź), y=startY, szerokość = cała szerokość, wysokość = cropHeight Bitmap croppedBitmap = Bitmap.createBitmap(toBeCropped, 0, startY, originalWidth, cropHeight); // Przykładowe użycie wyciętej bitmapy // mPreviewHalf.setImageBitmap(croppedBitmap); 

To podejście pozwala na dynamiczne przycinanie obrazów o dowolnych rozmiarach, zawsze uzyskując dolną część, niezależnie od oryginalnych wymiarów. Ważne jest, aby pamiętać o obsłudze przypadków brzegowych, takich jak bardzo małe obrazy, aby uniknąć błędów związanych z zerowymi lub ujemnymi wymiarami.

How to crop image in Android?
Bitmap.creatBitmap () is a great tool to crop image in Android. Bitmap.createBitmap (source file, x-coordinate, y-coordinate, rectangle width, rectangle height ); Here is the picture so you can easily understand the function. For example, if I want to crop the image, dog.jpg into four pieces. I will do like this process:

Maskowanie i Wycinanie (Clipping) za Pomocą PorterDuff.Mode

Oprócz prostokątnego przycinania, często potrzebujemy bardziej zaawansowanych technik manipulacji kształtem obrazu, takich jak maskowanie nieregularnych kształtów lub wycinanie „dziur” w bitmapie. Do tego celu w Androidzie często wykorzystuje się klasę Canvas w połączeniu z Paint i PorterDuff.Mode.

Podstawy PorterDuff.Mode

PorterDuff.Mode to zestaw trybów kompozycji, które określają, jak piksele źródłowe (nowo rysowane) są łączone z pikselami docelowymi (istniejącymi na płótnie). W kontekście wycinania, tryb PorterDuff.Mode.CLEAR jest szczególnie użyteczny. Działa on jak „gumka”, efektywnie usuwając piksele w obszarze, w którym jest stosowany, pozostawiając przezroczystość.

Przygotowanie do Maskowania

Aby zamaskować bitmapę, potrzebujemy: zmiennej bitmapy (na której będziemy rysować), obiektu Canvas skojarzonego z tą bitmapą oraz obiektu Paint skonfigurowanego z odpowiednim Xfermode (trybem transferu).

Kroki do wykonania maskowania:

  1. Pobierz wymiary ImageView, aby wiedzieć, jaki rozmiar ma mieć bitmapa.
  2. Utwórz zmienną bitmapę (lub skopiuj istniejącą na zmienną).
  3. Skojarz obiekt Canvas z tą bitmapą.
  4. Utwórz obiekt Paint i ustaw jego Xfermode na PorterDuff.Mode.CLEAR.
  5. Narysuj na Canvas ścieżkę (Path) reprezentującą obszar, który chcesz usunąć.
  6. Ustaw zmodyfikowaną bitmapę jako źródło obrazu w ImageView.

Poniżej znajduje się przykład kodu, który wycina centralną, zaokrągloną prostokątną „dziurę” w bitmapie:

// W metodzie addBackgroundToImage() private fun addBackgroundToImage() { val imageWidth = image.measuredWidth val imageHeight = image.measuredHeight // Pobieramy Drawable i konwertujemy na zmienną Bitmapę // .mutate() jest ważne, aby nie modyfikować oryginalnego Drawable, które może być współdzielone val background = ContextCompat.getDrawable(this, R.drawable.background)?.mutate() ?.toBitmap(imageWidth, imageHeight) background?.let { val clippedBackground = clipCenterPartOfBackgroundImage(it) image.setImageBitmap(clippedBackground) } } // Metoda do clippingu private fun clipCenterPartOfBackgroundImage(background: Bitmap): Bitmap { // Tworzymy nowy Canvas oparty na zmiennej bitmapie 'background'. // Ważne: bitmapa 'background' musi być zmienna (mutable), aby móc na niej rysować. val canvas = Canvas(background) val paint = Paint() paint.isAntiAlias = true // Wygładzanie krawędzi paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) // Tryb 'gumki' // Rysujemy ścieżkę, która zostanie 'wyczyszczona' na płótnie. canvas.drawPath(getSeeThroughPath(background.width, background.height), paint) return background } // Metoda definiująca ścieżkę do wycięcia private fun getSeeThroughPath(width: Int, height: Int): Path { val seeThroughLeft = width / 4f val seeThroughRight = width * 3f / 4f val seeThroughTop = height / 4f val seeThroughBottom = height * 3f / 4f val path = Path() // Dodajemy zaokrąglony prostokąt do ścieżki // Note: addRoundRect jest dostępny od API 21 path.addRoundRect( seeThroughLeft, seeThroughTop, seeThroughRight, seeThroughBottom, 16f, 16f, Path.Direction.CW ) path.close() // Zamykamy ścieżkę return path } 

Warto zwrócić uwagę, że użycie .toBitmap(imageWidth, imageHeight) na obiekcie Drawable z biblioteki androidx.core:core-ktx automatycznie tworzy zmienną bitmapę o określonych wymiarach. Jeśli pracujesz z istniejącą niezmienną bitmapą, musisz najpierw utworzyć jej zmienną kopię za pomocą metody copy() z parametrem isMutable = true przed przekazaniem jej do Canvas.

Zarządzanie Pamięcią i Wydajnością Bitmap

Bitmapy mogą zużywać znaczną ilość pamięci, szczególnie w przypadku obrazów o wysokiej rozdzielczości. Niewłaściwe zarządzanie nimi może prowadzić do błędów OutOfMemoryError (OOM) i spadku wydajności aplikacji. Android oferuje kilka mechanizmów i wskazówek, które pomagają w efektywnym zarządzaniu pamięcią.

Recykling Bitmap (Recycle)

public void recycle ()

Metoda recycle() jest przeznaczona do zwolnienia pamięci natywnej skojarzonej z bitmapą. Po jej wywołaniu, bitmapa jest oznaczana jako „martwa” i próby dostępu do jej pikseli (np. przez getPixels() lub setPixels()) spowodują błąd IllegalStateException. Jest to operacja nieodwracalna. Zwykle nie ma potrzeby ręcznego wywoływania recycle(), ponieważ normalny proces Garbage Collection (GC) w Javie/Kotlinie zajmie się zwolnieniem pamięci, gdy nie ma już żadnych referencji do obiektu bitmapy. Jednak w specyficznych, zaawansowanych scenariuszach (np. gdy zarządzasz dużą liczbą bitmap w pamięci podręcznej i potrzebujesz natychmiast zwolnić zasoby), może być to przydatne.

Przygotowanie do Rysowania (prepareToDraw)

public void prepareToDraw ()

Od API poziomu 24 (Nougat), wywołanie tej metody inicjuje asynchroniczne przesyłanie bitmapy do GPU na wątku renderującym (RenderThread), jeśli bitmapa nie została jeszcze przesłana. W przypadku akceleracji sprzętowej bitmapy muszą zostać przesłane do GPU, aby mogły być renderowane. Domyślnie dzieje się to przy pierwszym rysowaniu bitmapy, ale proces ten może zająć kilka milisekund. Wywołanie prepareToDraw() z wyprzedzeniem (np. na wątku roboczym po dekodowaniu obrazu) może zaoszczędzić czas w pierwszej klatce, w której bitmapa jest używana, poprawiając płynność interfejsu użytkownika.

How many Cartoon Pizza Slice stock illustrations are there?
Browse 8,100+ cartoon pizza slice stock illustrations and vector graphics available royalty-free, or start a new search to explore more great stock images and vector art. Pepperoni pizza with slice Pepperoni pizza icon set with slice. Simple cartoon style vector illustration. cartoon pizza slice stock illustrations

Zmiana Konfiguracji (reconfigure)

public void reconfigure (int width, int height, Bitmap.Config config)

Od API poziomu 19, ta metoda pozwala na modyfikację szerokości, wysokości i konfiguracji bitmapy bez wpływu na bazową alokację pamięci. Jest to potężne narzędzie do ponownego wykorzystania pamięci już przydzielonej dla bitmapy dla nowej konfiguracji, która zajmuje tyle samo lub mniej miejsca. Ostrzeżenie: Ta metoda nie powinna być wywoływana na bitmapie, która jest aktualnie używana przez system widoków, Canvas lub NDK API, ponieważ może to prowadzić do nieprzewidywalnych zachowań.

Pamięć i Rozmiar Bajtów

  • getByteCount(): Zwraca minimalną liczbę bajtów potrzebną do przechowywania pikseli bitmapy. Od API 19 (KitKat), wynik tej metody nie może być już używany do określania całkowitego zużycia pamięci przez bitmapę.
  • getAllocationByteCount() (niezawarte w dostarczonej dokumentacji, ale ważne): Zwraca całkowitą liczbę bajtów przydzielonych dla tej bitmapy. Jest to prawdziwa miara zużycia pamięci.

Często Zadawane Pytania (FAQ)

1. Czym różni się Bitmap.Config.ARGB_8888 od RGB_565?

ARGB_8888 używa 4 bajtów na piksel (8 bitów dla każdego kanału: Alfa, Czerwony, Zielony, Niebieski), co pozwala na pełną przezroczystość i 16 milionów kolorów. RGB_565 używa 2 bajtów na piksel (5 bitów dla Czerwonego, 6 dla Zielonego, 5 dla Niebieskiego), co oznacza brak kanału alfa i mniejszą paletę kolorów. RGB_565 zużywa mniej pamięci i jest szybszy, ale nadaje się tylko do obrazów bez przezroczystości, gdzie niewielka utrata jakości kolorów jest akceptowalna.

2. Kiedy powinienem użyć recycle() dla bitmapy?

Zazwyczaj nie musisz ręcznie wywoływać recycle(). System Androidowy Garbage Collector jest wystarczająco sprytny, aby zająć się zwalnianiem pamięci po bitmapach, gdy nie są już używane. Jednak w przypadku aplikacji, które intensywnie używają dużej liczby bitmap (np. galerii zdjęć), lub gdy szybko przełączasz się między dużymi obrazami, ręczne wywołanie recycle() może pomóc w zapobieganiu błędom OutOfMemoryError poprzez natychmiastowe zwolnienie pamięci natywnej. Pamiętaj, że po wywołaniu recycle(), bitmapa staje się bezużyteczna.

3. Czy createBitmap() zawsze tworzy kopię bitmapy?

Nie zawsze. Jeśli użyjesz createBitmap(Bitmap source) lub createBitmap(Bitmap source, int x, int y, int width, int height), a oryginalna bitmapa jest niezmienna (immutable) i żądany podzbiór jest identyczny z oryginalną bitmapą, system może zwrócić tę samą instancję source bez tworzenia nowej kopii. W innych przypadkach (np. gdy źródło jest zmienne, lub gdy wycinasz tylko fragment), tworzona jest nowa kopia.

4. Dlaczego Bitmap.Config.HARDWARE jest zawsze niezmienny?

Bitmapy o konfiguracji Config.HARDWARE są optymalizowane do renderowania sprzętowego. Są one przechowywane w pamięci GPU i zarządzane przez sprzęt graficzny, co sprawia, że są bardzo wydajne w rysowaniu. Ze względu na ich naturę i sposób zarządzania przez sprzęt, nie można ich modyfikować bezpośrednio po utworzeniu. Jeśli potrzebujesz zmodyfikować bitmapę sprzętową, musisz utworzyć jej zmienną kopię w innej konfiguracji, dokonać modyfikacji, a następnie ewentualnie ponownie utworzyć bitmapę sprzętową.

5. Jakie są konsekwencje nieustawienia isMutable = true przy kopiowaniu, jeśli chcę rysować na bitmapie?

Jeśli spróbujesz utworzyć obiekt Canvas z niezmienną bitmapą (gdzie isMutable() zwraca false), system zgłosi błąd IllegalStateException. Canvas wymaga, aby bitmapa, na której ma rysować, była zmienna. Dlatego zawsze upewnij się, że Twoja bitmapa jest zmienna, jeśli planujesz na niej rysować.

Praca z bitmapami w Androidzie jest nieodłącznym elementem tworzenia atrakcyjnych i wydajnych aplikacji. Zrozumienie bogactwa metod oferowanych przez klasę Bitmap, a także świadome zarządzanie pamięcią i wydajnością, pozwoli Ci tworzyć płynne i responsywne interfejsy użytkownika. Niezależnie od tego, czy tworzysz prostą aplikację do przeglądania zdjęć, czy złożoną grę, opanowanie tych technik jest kluczem do sukcesu.

Zainteresował Cię artykuł Kompletny Przewodnik po Bitmapach w Androidzie? Zajrzyj też do kategorii Gastronomia, znajdziesz tam więcej podobnych treści!

Go up