poniedziałek, 17 lutego 2014

Wykorzystanie aparatu fotograficznego do robienia zdjęć


Kod źródłowy do tej lekcji znajduje się tutaj. Rozpakuj go i zaimportuj. W katalogu z kodem źródłowym znajdą się podkatalogi oznaczone numerami. Są to po prostu kolejne wersje kodu, tworzonego w ramach przykładów. Możesz analizować kolejne kroki tworzenia aplikacji, lub od razu zabrać się za kod z katalogu o najwyższym numerku.


Podobnie jak w przypadku czujników, do kamery również mamy gotowy interfejs. Zostały stworzone odpowiednie klasy i metody, wystarczy tylko z nich skorzystać. Zaczniemy od najprostszego programu robiącego zdjęcie i wyświetlające je na komponencie klasy ImageView. Przyklejam na głównej aktywności jeden przycisk i jeden imageView:



Po naciśnięciu tego przycisku zostanie uruchomiony wbudowany program do robienia zdjęć, a następnie odbierzemy zrobione zdjęcie i wyświetlimy je na ImageView. Poniżej wklejam kod aktywności związanej z tym ekranem. W liniach 26-34 oprogramowuję zdarzenie naciśnięcia przycisku. Część która jest związana z samym robieniem zdjęć jako takim to linie 30 i 31. Tworzę nową intencję której zadaniem jest wywołanie wbudowanego w Androida programu do robienia zdjęć (linia 30). W kolejnej linii uruchamiam tę intencję. Wywołanie takiej intencji wygląda nieco inaczej niż to do czego się przyzwyczailiśmy. Zazwyczaj wywoływaliśmy metodę startActivity(Intent i), a tutaj pojawia się startActivityForResult(Intent i, int x). Sama w sobie metoda zakłada że wywoływana intencja coś nam odda. Przy zakończeniu działania intencji i zwrocie danych wywoływana jest metoda onActivityResult (o tym za chwilę).Jako parametry podajemy intencję która ma zostać uruchomiona, oraz identyfikator wywołania. Poprzez drugi parametr podajemy wartość (parametr typu int) która później zostanie zwrócona po zrobieniu zdjęcia. Przydatne gdybyśmy np. wywołali kilka różnych intencji oczekując różnych zwracanych wyników. Każda z takich intencji wywoła metodę onActivityResult (która parę linii niżej implementujemy) w której możemy później po takim identyfikatorze dojść z której to intencji/wywołania intencji zostały zwrócone dane.



W liniach 38-44 implementuję metodę onActivityResult która jest automagicznie wywoływana w momencie kiedy nasza intencja wywołana przy użyciu metody startActivityForResult kończy swój przebieg. W tej właśnie metodzie odbieramy zrobione zdjęcie. Przyjrzyjmy się warunkom w linii 39. Sprawdzam parametr requestCode. To jest właśnie parametr który przekazałem przy wywoływaniu intencji (linia 31). Sprawdzam czy requestCode jest równe 1, by upewnić się że to jest wywołanie intencji związane z robieniem zdjęcia (mogłem przeciez wywołać wcześniej kilka różnych intencji i oczekiwać na wynik). Drugi parametr – resultCode jest zwracany przez wywoływaną aktywność – tutaj wbudowany program do robienia zdjęć. RESULT_OK jest zwracane w przypadku zatwierdzenia zdjęcia. Może też zostać zwrócone RESULT_CANCEL jeśli ktoś anuluje robienie zdjęcia. W liniach 40 i 41 odbieram zrobione zdjęcie. Aby odebrać fotkę muszę zawsze odwołać się w wiązce do identyfikatora „data”. Linia 42 to już po prostu ustawienie odebranego zdjęcia jako obrazek dla komponentu ImageView. Wykorzystanie kamery wymaga odpowiednich pozwoleń, dlatego dodajemy je do pliku manifestu ( AndroidManifest.xml) aplikacji w ten sposób:

</application>

<uses-permission android:name="android.permission.CAMERA" />


</manifest>


Po uruchomieniu i naciśnięciu przycisku pojawia nam się wbudowany programik do robienia fotografii:




Zapomniałem „dziubka” zrobić, to bym sobie na „fejsbuczka” wstawił „słit focię”. Po zrobieniu zdjęcia, dolny panel powinien nam się zamienić na taki:




Mogę tutaj anulować operację, powtórzyć robienie zdjęcia, lub zatwierdzić. Zatwierdzamy, a po powrocie z tej aktywności nasz ekran wygląda tak:




Za wyjątkiem może zdjęcia, na swoim zobaczysz pewnie coś ładniejszego :p Przy użyciu tej metody możemy niestety robić tylko takie małe zdjęcia. Za chwilę zrobimy to troszkę inaczej i dostaniemy pełnowymiarowe zdjęcie.

Stworzyłem nową aplikację. Aby zrobić pełnowymiarowe zdjęcie, Android będzie musiał zapisać je w pliku. Podobnie jak wcześniej dodaję prośbę o uprawnienia do kamery pliku manifestu, teraz jednak muszę też uzyskać pozwolenie na korzystanie z karty pamięci, lub jakiegoś innego folderu.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


Android udostępnia publicznie dostępny katalog (dostępny dla wszystkich aplikacji). Aby się do niego dobrać możemy zastosować np. taką konstrukcję:

File katalog = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

Jak zawsze rozpoczynam pracę od przyklejenia komponentów:




Dałem jeden guzik który uruchomi intencję, oraz jeden TextView na którym znajdzie się ścieżka do zrobionej fotografii.
W aktywności, w metodzie onCreate podpinam referencję do komponentów i jako obsługę naciśnięcia przycisku wywołuję metodę obslugaGuzika().



Sama metoda „obslugaGuzika” jest przestawiona poniżej:




Wyjaśniam rzeczy których nie omawiałem wcześniej. W linii 50 tworzę plik który wykorzystam za chwilę do zapisania fotografii na dysku. Linie 51-54 służą jedynie nazwaniu pliku tak, by nazwa zawierała datę i czas zrobienia zdjęcia. Jeśli chcesz, możesz je nazwać po prostu „fotka”. Linia 57 to pobranie referencji do kyatalogu przeznaczonego na obrazki. W linii 59 tworzę plik w którym znajdzie się zdjęcie. W linii 63 do intencji podaję dodatkowy parametr tj. ścieżkę do pliku w którym ma się znaleźć zdjęcie. Po zakończeniu cyklu intencji wyświetlam na textView ścieżkę do utworzonego pliku.
Efekt:





Może nam nie odpowiadać to, że musimy dodatkowo klikać na androidowej aktywności do robienia zdjęć. Chcielibyśmy by przykładowo program sam robił zdjęcie w reakcji na jakieś zdarzenie – np. pobudzenie czujnika ruchu. Konieczność dodatkowego kliknięcia przez człowieka w takim wypadku nie wchodzi w grę. Jest możliwość automatyczniego robienia zdjęć, bez wywoływania dodatkowej systemowej aktywności, ale wymaga to więcej pracy.

Stworzyłem nowy projekt i przykleiłem na ekranie button i imageView:



Założenie jest takie, że zaraz po kliknięciu przycisku zostanie wykonana fotografia i wyświetlona na imageView. Muszę też zadbać o wymagane pozwolenie:


<uses-permission android:name="android.permission.CAMERA"/>


Przechodzimy teraz do kodu.




Aby automatycznie odbierać robione zdjęcie, musimy w klasie która ma je odbierać implementować PictureCallback (linia 26). To sprawi, że w momencie zrobienia zdjęcia zostanie wywołana metoda onPictureTaken do której zostanie przekazane zdjęcie w postaci tablicy elementów typu byte... Do tej metody przejdziemy za kilka chwil. Linie 37,38 to podpięcie referencji do komponentów, nic nadzwyczajnego. Kod z linii 41-51 ma na celu sprawdzenie czy urządzenie w ogóle posiada kamerę (linia 41) oraz znalezienie jej identyfikatora i uruchomienie. W linii 45 do zmiennej liczbowej cameraId przypisuję identyfikator kamery zwracany jest przez metodę znajdzKamere. Zwraca ona wartość większą niż -1 jeśli zostanie znaleziona tylna kamera, jeśli nie zostanie znaleziona -metoda zwraca wartość -1. To mam nadzieję że wyjaśnia warunek if z linii 46. Kodem samej metody zajmiemy się za chwilę. Linia 49 to uruchomienie kamery dla identyfikatora zwróconego przez metodę znajdzKamere. Przejdźmy dalej:




Linie 53-61 to podpięcie wywołania mojej metody onC pod kliknięcie przycisku. W liniach 64-67 znajdziemy metodę, która powoduje wykonanie zdjęcia przez kamerę. Istotnym z punktu widzenia funkcjonalności jest tutaj wywołanie metody takePicture dla obiektu klasy Camera dla którego wcześniej (linia 49) podpinaliśmy identyfikator fizycznego urządzenia. Linie 70 – 81 to metoda zwracająca identyfikator tylnej kamery. Wywoływaliśmy ją wcześniej (linia 45) w celu odnalezienia identyfikatora kamery. Zasada działania jest prosta. Jest pętla, której zawartość jest wykonywana tyle razy, ile jest kamer w systemie. Przy każdym „obrocie” sprawdzam czy właśnie sprawdzana kamera to ta odpowiadająca identyfikatorowi tylnej kamery (CameraInfo.CAMERA_FACING_BACK w linii 75). Gdybyśmy zechcieli robić zdjęcia z użyciem kamery przedniej, możemy wykorzytać identyfikator CAMERA_FACING_FRONT. Przejdźmy do dalszej części kodu:



Linie 85-90 to odblokowanie kamery w przypadku np. „minimalizacji” programu, tak by inne programy również mogły korzystać z tego urządzenia. Przesłaniam tutaj domyślną metodę onPause klasy Activity. Linie 94-97 to implementacja metody onPictureTaken (konieczność wymaga z faktu że implementujemy interfejs PictureCallBack. Ta metoda jest automatycznie wywoływana w momencie zrobienia zdjęcia. Przy użyciu metody decodeByteArray klasy BitmapFactory (linia 95) konwertuję obraz w postaci tablicy elementów typu byte do bitmapy. Linia 96 to wyświetlenie fotki na komponencie klasy ImageView. Co z tego wszystkiego jest najważniejsze? Najważniejsze elementy:
Implementujemy interfejs PictureCallback (linia 26)
Podpięcie urządzenia pod obiekt klasy camera (linia 45)
Wywołanie metody takePicture obiektu klasy Camera wtedy kiedy chcemy zrobić zdjęcie (linia 65)
Implementacja automatycznie wywoływanej metody onPictureTaken i przetworzenie przekazanego nam przez argument zdjęcia (linia 94).

Te elementy to nasz „engine”, reszta to poboczny osprzęt.

Efekt po uruchomieniu i kliknięciu przycisku:


1 komentarz:

  1. Ostatni przykład u mnie nie działa, wysypuje się po naciśnięciu przycisku

    08-30 10:42:38.132: D/Logi(10347): Jest kamera... jakaś...
    08-30 10:42:38.132: D/Trololo(10347): Znaleziono kamerę tylną
    08-30 10:42:38.402: I/Adreno-EGL(10347): : EGL 1.4 QUALCOMM Build: I0404c4692afb8623f95c43aeb6d5e13ed4b30ddbDate: 11/06/13
    08-30 10:42:38.432: D/OpenGLRenderer(10347): Enabling debug mode 0
    08-30 10:42:50.584: D/AndroidRuntime(10347): Shutting down VM
    08-30 10:42:50.584: W/dalvikvm(10347): threadid=1: thread exiting with uncaught exception (group=0x4158bba8)
    08-30 10:42:50.584: E/AndroidRuntime(10347): FATAL EXCEPTION: main
    08-30 10:42:50.584: E/AndroidRuntime(10347): Process: com.example.automatycznefoto, PID: 10347
    08-30 10:42:50.584: E/AndroidRuntime(10347): java.lang.RuntimeException: takePicture failed
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.hardware.Camera.native_takePicture(Native Method)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.hardware.Camera.takePicture(Camera.java:1244)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.hardware.Camera.takePicture(Camera.java:1189)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at com.example.automatycznefoto.MainActivity.onC(MainActivity.java:65)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at com.example.automatycznefoto.MainActivity$1.onClick(MainActivity.java:57)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.view.View.performClick(View.java:4438)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.view.View$PerformClick.run(View.java:18422)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.os.Handler.handleCallback(Handler.java:733)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.os.Handler.dispatchMessage(Handler.java:95)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.os.Looper.loop(Looper.java:136)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at android.app.ActivityThread.main(ActivityThread.java:5017)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at java.lang.reflect.Method.invokeNative(Native Method)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at java.lang.reflect.Method.invoke(Method.java:515)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
    08-30 10:42:50.584: E/AndroidRuntime(10347): at dalvik.system.NativeStart.main(Native Method)

    OdpowiedzUsuń