หัวข้อนี้จะแสดงวิธีตั้งค่ากรณีการใช้งาน CameraX ภายในแอปเพื่อรับรูปภาพที่มีข้อมูลการหมุนที่ถูกต้อง ไม่ว่าจะเป็นจากกรณีการใช้งาน ImageAnalysis
หรือ ImageCapture
ดังนั้น
Analyzer
ของ Use CaseImageAnalysis
ควรได้รับเฟรมที่มีการหมุนที่ถูกต้อง- Use Case
ImageCapture
ควรถ่ายภาพโดยหมุนให้ถูกต้อง
คำศัพท์
หัวข้อนี้ใช้คำศัพท์ต่อไปนี้ คุณจึงควรทำความเข้าใจความหมายของคำแต่ละคำ
- การวางแนวจอแสดงผล
- ค่านี้หมายถึงด้านใดของอุปกรณ์ที่อยู่ในตำแหน่งขึ้น และอาจเป็นค่าใดค่าหนึ่งต่อไปนี้ แนวตั้ง แนวนอน แนวตั้งกลับด้าน หรือแนวนอนกลับด้าน
- การหมุนจอแสดงผล
- คือค่าที่
Display.getRotation()
แสดงผล และแสดงองศาที่อุปกรณ์หมุนทวนเข็มนาฬิกาจากการวางแนวตามปกติ - การหมุนเวียนเป้าหมาย
- ค่านี้แสดงจำนวนองศาในการหมุนอุปกรณ์ตามเข็มนาฬิกาเพื่อให้อยู่ในแนวตั้งตามปกติ
วิธีกำหนดการหมุนเป้าหมาย
ตัวอย่างต่อไปนี้แสดงวิธีกำหนดการหมุนเป้าหมายสำหรับอุปกรณ์โดยอิงตามการวางแนวตามปกติ
ตัวอย่างที่ 1: การวางแนวตั้งตามปกติ
ตัวอย่างอุปกรณ์: Pixel 3 XL | |
---|---|
การวางแนวตามปกติ = แนวตั้ง การหมุนจอแสดงผล = 0 |
|
การวางแนวตามปกติ = แนวตั้ง การหมุนจอแสดงผล = 90 |
ตัวอย่างที่ 2: การวางแนวนอนตามธรรมชาติ
ตัวอย่างอุปกรณ์: Pixel C | |
---|---|
การวางแนวตามปกติ = แนวนอน การหมุนจอแสดงผล = 0 |
|
การวางแนวตามปกติ = แนวนอน การหมุนของจอแสดงผล = 270 |
การหมุนภาพ
ปลายสายใดที่เปิดอยู่ การวางแนวเซ็นเซอร์จะกำหนดใน Android เป็นค่าคงที่ ซึ่งแสดงองศา (0, 90, 180, 270) ที่เซ็นเซอร์หมุนจากด้านบนของอุปกรณ์เมื่ออุปกรณ์อยู่ในตำแหน่งปกติ สำหรับกรณีทั้งหมดในแผนภาพ การหมุนรูปภาพจะอธิบายวิธีหมุนข้อมูลตามเข็มนาฬิกาเพื่อให้ปรากฏในแนวตั้ง
ตัวอย่างต่อไปนี้แสดงลักษณะการหมุนของภาพโดยขึ้นอยู่กับการวางแนวเซ็นเซอร์กล้อง และยังถือว่ามีการตั้งค่าการหมุนเป้าหมายเป็นการหมุนของจอแสดงผล
ตัวอย่างที่ 1: เซ็นเซอร์หมุน 90 องศา
ตัวอย่างอุปกรณ์: Pixel 3 XL | |
---|---|
การหมุนจอแสดงผล = 0 |
|
การหมุนจอแสดงผล = 90 |
ตัวอย่างที่ 2: เซ็นเซอร์หมุน 270 องศา
ตัวอย่างอุปกรณ์: Nexus 5X | |
---|---|
การหมุนจอแสดงผล = 0 |
|
การหมุนจอแสดงผล = 90 |
ตัวอย่างที่ 3: เซ็นเซอร์หมุน 0 องศา
ตัวอย่างอุปกรณ์: Pixel C (แท็บเล็ต) | |
---|---|
การหมุนจอแสดงผล = 0 |
|
การหมุนจอแสดงผล = 270 |
การคํานวณการหมุนของรูปภาพ
ImageAnalysis
Analyzer
ของ ImageAnalysis
รับรูปภาพจากกล้องในรูปแบบของ ImageProxy
รูปภาพแต่ละรูปมีข้อมูลการหมุน ซึ่งเข้าถึงได้ผ่าน
val rotation = imageProxy.imageInfo.rotationDegrees
ค่านี้แสดงองศาที่รูปภาพต้องหมุนตามเข็มนาฬิกาเพื่อให้ตรงกับการหมุนเป้าหมายของ ImageAnalysis
ในบริบทของแอป Android โดยปกติแล้วการหมุนเป้าหมายของ ImageAnalysis
จะตรงกับการวางแนวของหน้าจอ
ImageCapture
มีการแนบการเรียกกลับกับอินสแตนซ์ ImageCapture
เพื่อส่งสัญญาณเมื่อผลการจับภาพพร้อมใช้งาน ผลลัพธ์อาจเป็นรูปภาพที่จับภาพไว้หรือข้อผิดพลาด
เมื่อถ่ายภาพ แคลลแบ็กที่ระบุอาจเป็นประเภทใดประเภทหนึ่งต่อไปนี้
OnImageCapturedCallback
: รับรูปภาพที่มีการเข้าถึงในหน่วยความจําในรูปแบบImageProxy
OnImageSavedCallback
: เรียกใช้เมื่อจัดเก็บรูปภาพที่จับภาพไว้เรียบร้อยแล้วในตำแหน่งที่ระบุโดยImageCapture.OutputFileOptions
ตัวเลือกสามารถระบุFile
,OutputStream
หรือตําแหน่งในMediaStore
การหมุนของรูปภาพที่จับภาพไว้ ไม่ว่ารูปแบบจะเป็นแบบใด (ImageProxy
,
File
, OutputStream
, MediaStore Uri
) จะแสดงองศาการหมุนที่รูปภาพที่จับภาพไว้ต้องหมุนตามเข็มนาฬิกาเพื่อให้ตรงกับการหมุนเป้าหมายของ ImageCapture
ซึ่งโดยทั่วไปแล้วในบริบทของแอป Android จะตรงกับการวางแนวของหน้าจอ
คุณสามารถเรียกข้อมูลการหมุนของรูปภาพที่จับภาพได้โดยใช้วิธีใดวิธีหนึ่งต่อไปนี้
ImageProxy
val rotation = imageProxy.imageInfo.rotationDegrees
File
val exif = Exif.createFromFile(file) val rotation = exif.rotation
OutputStream
val byteArray = outputStream.toByteArray() val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) val rotation = exif.rotation
MediaStore uri
val inputStream = contentResolver.openInputStream(outputFileResults.savedUri) val exif = Exif.createFromInputStream(inputStream) val rotation = exif.rotation
ยืนยันการหมุนของรูปภาพ
Use Case ImageAnalysis
และ ImageCapture
จะได้รับ ImageProxy
จากกล้องหลังจากส่งคำขอจับภาพสำเร็จ ImageProxy
จะรวมรูปภาพและข้อมูลเกี่ยวกับรูปภาพนั้นไว้ด้วยกัน รวมถึงการหมุน ข้อมูลการหมุนนี้แสดงองศาที่รูปภาพต้องหมุนเพื่อให้ตรงกับการหมุนเป้าหมายของกรณีการใช้งาน
หลักเกณฑ์การหมุนเป้าหมายของ ImageCapture/ImageAnalysis
เนื่องจากอุปกรณ์จำนวนมากไม่หมุนกลับเป็นแนวตั้งหรือแนวนอนโดยค่าเริ่มต้น แอป Android บางแอปจึงไม่รองรับการวางแนวเหล่านี้ การที่แอปรองรับหรือไม่รองรับฟีเจอร์นี้จะเปลี่ยนวิธีอัปเดตการหมุนเป้าหมายของ Use Case
ด้านล่างนี้คือ 2 ตารางที่ระบุวิธีทำให้การหมุนเป้าหมายของ Use Case ซิงค์กับการหมุนของจอแสดงผล รายการแรกแสดงวิธีดำเนินการโดยรองรับการวางแนวทั้ง 4 แบบ ส่วนรายการที่ 2 จะจัดการเฉพาะการวางแนวที่อุปกรณ์หมุนไปโดยค่าเริ่มต้นเท่านั้น
วิธีเลือกหลักเกณฑ์ที่จะปฏิบัติตามในแอป
ยืนยันว่ากล้อง
Activity
ของแอปมีการล็อกการวางแนว การวางแนวที่ปลดล็อก หรือลบล้างการเปลี่ยนแปลงการกำหนดค่าการวางแนวหรือไม่ตัดสินใจว่า
Activity
กล้องของแอปควรรองรับการวางแนวของอุปกรณ์ทั้ง 4 แบบ (แนวตั้ง แนวตั้งกลับด้าน แนวนอน และแนวนอนกลับด้าน) หรือควรรองรับเฉพาะการวางแนวที่อุปกรณ์ที่ใช้งานรองรับโดยค่าเริ่มต้น
รองรับการวางแนวทั้ง 4 แบบ
ตารางนี้กล่าวถึงหลักเกณฑ์บางอย่างที่ควรทำในกรณีที่อุปกรณ์ไม่หมุนเป็นแนวตั้งกลับหัว การดำเนินการนี้ใช้ได้กับอุปกรณ์ที่ไม่หมุนเป็นแนวนอนกลับหัว
สถานการณ์ | หลักเกณฑ์ | โหมดหน้าต่างเดียว | โหมดแยกหน้าจอหลายหน้าต่าง |
---|---|---|---|
การวางแนวที่ปลดล็อกอยู่ |
ตั้งค่ากรณีการใช้งานทุกครั้งที่สร้าง Activity เช่น ใน onCreate() callback ของ Activity
|
||
ใช้ onOrientationChanged() ของ OrientationEventListener
ในการเรียกกลับ ให้อัปเดตการหมุนเวียนเป้าหมายของ Use Case ซึ่งจะจัดการในกรณีที่ระบบไม่สร้าง Activity ขึ้นมาใหม่แม้ว่าจะมีการเปลี่ยนแปลงการวางแนวแล้ว เช่น เมื่อหมุนอุปกรณ์ 180 องศา
|
รวมถึงจัดการเมื่อจอแสดงผลอยู่ในแนวตั้งกลับหัวและอุปกรณ์ไม่หมุนเป็นแนวตั้งกลับหัวโดยค่าเริ่มต้น |
รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
|
|
ไม่บังคับ: ตั้งค่าพร็อพเพอร์ตี้ screenOrientation ของ Activity เป็น fullSensor ในไฟล์ AndroidManifest
|
วิธีนี้ช่วยให้ UI ตั้งตรงเมื่ออุปกรณ์อยู่ในแนวตั้งกลับหัว และช่วยให้ระบบสร้าง Activity ขึ้นมาใหม่ทุกครั้งที่อุปกรณ์หมุน 90 องศา
|
ไม่ส่งผลต่ออุปกรณ์ที่ไม่ได้หมุนเป็นแนวตั้งโดยค่าเริ่มต้น ระบบไม่รองรับโหมดหลายหน้าต่างขณะที่จอแสดงผลอยู่ในการวางแนวตั้งกลับหัว | |
การวางแนวที่ล็อกอยู่ |
ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ในการเรียกกลับ onCreate() ของ Activity
|
||
ใช้ onOrientationChanged() ของ OrientationEventListener
ในการเรียกกลับ ให้อัปเดตการหมุนเป้าหมายของ Use Case ยกเว้นตัวอย่าง
|
รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
|
||
Orientation configChanges overridden |
ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ในการเรียกกลับ onCreate() ของ Activity
|
||
ใช้ onOrientationChanged() ของ OrientationEventListener
ในการเรียกกลับ ให้อัปเดตการหมุนเวียนเป้าหมายของ Use Case
|
รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
|
||
ไม่บังคับ: ตั้งค่าพร็อพเพอร์ตี้ screenOrientation ของกิจกรรมเป็น fullSensor ในไฟล์ AndroidManifest | ช่วยให้ UI ตั้งตรงเมื่ออุปกรณ์อยู่ในแนวตั้งกลับหัว | ไม่ส่งผลต่ออุปกรณ์ที่ไม่ได้หมุนเป็นแนวตั้งโดยค่าเริ่มต้น ระบบไม่รองรับโหมดหลายหน้าต่างขณะที่จอแสดงผลอยู่ในแนวตั้งกลับหัว |
รองรับเฉพาะการวางแนวที่อุปกรณ์รองรับ
รองรับเฉพาะการวางแนวที่อุปกรณ์รองรับโดยค่าเริ่มต้น (ซึ่งอาจหรือไม่รวมถึงการวางแนวตั้ง/แนวนอนกลับด้าน)
สถานการณ์ | หลักเกณฑ์ | โหมดแยกหน้าจอหลายหน้าต่าง |
---|---|---|
การวางแนวที่ปลดล็อก |
ตั้งค่ากรณีการใช้งานทุกครั้งที่สร้าง Activity เช่น ใน onCreate() callback ของ Activity
|
|
ใช้ onDisplayChanged() ของ DisplayListener ในการเรียกกลับ ให้อัปเดตการหมุนเป้าหมายของ Use Case เช่น เมื่ออุปกรณ์หมุน 180 องศา
|
รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
|
|
การวางแนวที่ล็อกอยู่ |
ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ในการเรียกกลับ onCreate() ของ Activity
|
|
ใช้ onOrientationChanged() ของ OrientationEventListener
ในการเรียกกลับ ให้อัปเดตการหมุนเวียนเป้าหมายของ Use Case
|
รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
|
|
Orientation configChanges overridden |
ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ในการเรียกกลับ onCreate() ของ Activity
|
|
ใช้ onDisplayChanged() ของ DisplayListener ในการเรียกกลับ ให้อัปเดตการหมุนเป้าหมายของ Use Case เช่น เมื่ออุปกรณ์หมุน 180 องศา
|
รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
|
การวางแนวที่ปลดล็อก
Activity
มีการกําหนดการวางแนวแบบปลดล็อกเมื่อการวางแนวของการแสดงผล (เช่น แนวตั้งหรือแนวนอน) ตรงกับการวางแนวของอุปกรณ์ ยกเว้นการวางแนวแนวตั้ง/แนวนอนกลับด้าน ซึ่งอุปกรณ์บางรุ่นไม่รองรับโดยค่าเริ่มต้น หากต้องการบังคับให้อุปกรณ์หมุนไปยังการวางแนวทั้ง 4 แบบ ให้ตั้งค่าพร็อพเพอร์ตี้ screenOrientation
ของ Activity
เป็น fullSensor
ในโหมดหลายหน้าต่าง อุปกรณ์ที่ไม่รองรับการกลับแนวตั้ง/แนวนอนโดยค่าเริ่มต้นจะไม่หมุนกลับเป็นแนวตั้ง/แนวนอน แม้ว่าจะตั้งค่าพร็อพเพอร์ตี้ screenOrientation
เป็น fullSensor
ก็ตาม
<!-- The Activity has an unlocked orientation, but might not rotate to reverse portrait/landscape in single-window mode if the device doesn't support it by default. --> <activity android:name=".UnlockedOrientationActivity" /> <!-- The Activity has an unlocked orientation, and will rotate to all four orientations in single-window mode. --> <activity android:name=".UnlockedOrientationActivity" android:screenOrientation="fullSensor" />
การวางแนวที่ล็อกอยู่
จอแสดงผลจะล็อกการวางแนวไว้เมื่ออยู่ในการวางแนวเดียวกัน (เช่น แนวตั้งหรือแนวนอน) ไม่ว่าอุปกรณ์จะวางในแนวใดก็ตาม ซึ่งทำได้โดยระบุพร็อพเพอร์ตี้ screenOrientation
ของ Activity
ภายในการประกาศในไฟล์ AndroidManifest.xml
เมื่อจอแสดงผลมีการวางแนวที่ล็อกไว้ ระบบจะไม่ทำลายและสร้าง Activity
ขึ้นมาใหม่เมื่อมีการหมุนอุปกรณ์
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
ลบล้างการเปลี่ยนแปลงการกำหนดค่าการวางแนว
เมื่อ Activity
ลบล้างการเปลี่ยนแปลงการกำหนดค่าการวางแนว ระบบจะไม่ทำลายและสร้างใหม่เมื่อการวางแนวของอุปกรณ์มีการเปลี่ยนแปลง
แต่ระบบจะอัปเดต UI ให้ตรงกับการวางแนวของอุปกรณ์
<!-- The Activity's UI might not rotate in reverse portrait/landscape if the device doesn't support it by default. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" /> <!-- The Activity's UI will rotate to all 4 orientations in single-window mode. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor" />
การตั้งค่ากรณีการใช้งานของกล้อง
ในสถานการณ์ที่อธิบายข้างต้น คุณตั้งค่า Use Case ของกล้องได้เมื่อสร้าง Activity
เป็นครั้งแรก
ในกรณีที่ Activity
มีการวางแนวที่ปลดล็อกอยู่ การตั้งค่านี้จะดำเนินการทุกครั้งที่อุปกรณ์หมุน เนื่องจากระบบจะทำลายและสร้าง Activity
ขึ้นมาใหม่เมื่อการวางแนวเปลี่ยนแปลง ผลที่ได้คือ Use Case จะตั้งค่าการหมุนเป้าหมายให้ตรงกับการวางแนวของจอแสดงผลโดยค่าเริ่มต้นทุกครั้ง
ในกรณีที่ Activity
มีการวางแนวที่ล็อกไว้หรือลบล้างการเปลี่ยนแปลงการกำหนดค่าการวางแนว การตั้งค่านี้จะทําเพียงครั้งเดียวเมื่อสร้าง Activity
เป็นครั้งแรก
class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val cameraProcessFuture = ProcessCameraProvider.getInstance(this) cameraProcessFuture.addListener(Runnable { val cameraProvider = cameraProcessFuture.get() // By default, the use cases set their target rotation to match the // display’s rotation. val preview = buildPreview() val imageAnalysis = buildImageAnalysis() val imageCapture = buildImageCapture() cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis, imageCapture) }, mainExecutor) } }
การตั้งค่า OrientationEventListener
การใช้ OrientationEventListener
ช่วยให้คุณอัปเดตการหมุนเป้าหมายของกรณีการใช้งานกล้องได้อย่างต่อเนื่องเมื่อการวางแนวของอุปกรณ์เปลี่ยนแปลง
class CameraActivity : AppCompatActivity() { private val orientationEventListener by lazy { object : OrientationEventListener(this) { override fun onOrientationChanged(orientation: Int) { if (orientation == ORIENTATION_UNKNOWN) { return } val rotation = when (orientation) { in 45 until 135 -> Surface.ROTATION_270 in 135 until 225 -> Surface.ROTATION_180 in 225 until 315 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } } override fun onStart() { super.onStart() orientationEventListener.enable() } override fun onStop() { super.onStop() orientationEventListener.disable() } }
การตั้งค่า DisplayListener
การใช้ DisplayListener
ช่วยให้คุณอัปเดตกรณีการใช้งานการหมุนเป้าหมายของกล้องได้ในบางสถานการณ์ เช่น เมื่อระบบไม่ทำลายและสร้าง Activity
อีกครั้งหลังจากที่อุปกรณ์หมุน 180 องศา
class CameraActivity : AppCompatActivity() { private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { if (rootView.display.displayId == displayId) { val rotation = rootView.display.rotation imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } override fun onDisplayAdded(displayId: Int) { } override fun onDisplayRemoved(displayId: Int) { } } override fun onStart() { super.onStart() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.registerDisplayListener(displayListener, null) } override fun onStop() { super.onStop() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.unregisterDisplayListener(displayListener) } }