1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.camera.fov;
18 
19 import android.app.Activity;
20 import android.content.SharedPreferences;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.Matrix;
26 import android.graphics.Paint;
27 import android.graphics.RectF;
28 import android.media.ExifInterface;
29 import android.os.Bundle;
30 import android.preference.PreferenceManager;
31 import android.view.SurfaceHolder;
32 import android.view.SurfaceView;
33 import android.view.View;
34 import android.widget.Button;
35 import android.widget.SeekBar;
36 import android.widget.SeekBar.OnSeekBarChangeListener;
37 
38 import com.android.cts.verifier.R;
39 
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.IOException;
43 
44 /**
45  * Shows the picture taken and lets the user specify the field of view (FOV).
46  */
47 public class DetermineFovActivity extends Activity {
48 
49     private static final float FOV_ADJUSTMENT_RANGE = 20;
50     private static final int SEEKBAR_MAX_VALUE = 100;
51     private static final float TEXT_SIZE = 16;
52     private static final float TEXT_PADDING = 0.2f;
53     private static final String DEFAULT_MARKER_DISTANCE = "36.8";
54     private static final String DEFAULT_TARGET_DISTANCE = "99.7";
55 
56     private float mMarkerDistanceCm;
57     private SurfaceView mSurfaceView;
58     private SurfaceHolder mSurfaceHolder;
59     private Bitmap mPhotoBitmap;
60     private float mFovMinDegrees;
61     private float mFovMaxDegrees;
62     private float mFovDegrees;
63     private float mReportedFovDegrees;
64     private SeekBar mSeekBar;
65     private Button mDoneButton;
66     private float mTargetDistanceCm;
67     private String mMeasuredText;
68     private String mReportedText;
69 
70     @Override
onCreate(Bundle savedInstanceState)71     protected void onCreate(Bundle savedInstanceState) {
72         super.onCreate(savedInstanceState);
73         setContentView(R.layout.camera_fov_calibration_determine_fov);
74         File pictureFile = PhotoCaptureActivity.getPictureFile(this);
75         try {
76             mPhotoBitmap =
77                     BitmapFactory.decodeStream(new FileInputStream(pictureFile));
78         } catch (IOException e) {
79             e.printStackTrace();
80         }
81 
82         int previewOrientation;
83         try {
84             ExifInterface exif = new ExifInterface(pictureFile);
85             int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
86                                                        ExifInterface.ORIENTATION_NORMAL);
87             switch (exifOrientation) {
88                 case ExifInterface.ORIENTATION_ROTATE_90: previewOrientation = 90; break;
89                 case ExifInterface.ORIENTATION_ROTATE_180: previewOrientation = 180; break;
90                 case ExifInterface.ORIENTATION_ROTATE_270: previewOrientation = 270; break;
91                 default: previewOrientation = 0; break;
92             }
93         } catch (IOException e) {
94             previewOrientation = 0;
95         }
96 
97         if (previewOrientation != 0) {
98             Matrix transform = new Matrix();
99             transform.setRotate(previewOrientation);
100             mPhotoBitmap = Bitmap.createBitmap(mPhotoBitmap, 0, 0, mPhotoBitmap.getWidth(),
101                                                mPhotoBitmap.getHeight(), transform, true);
102         }
103 
104         mSurfaceView = (SurfaceView) findViewById(R.id.camera_fov_photo_surface);
105         mSurfaceHolder = mSurfaceView.getHolder();
106         mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
107             @Override
108             public void surfaceDestroyed(SurfaceHolder holder) {}
109 
110         @Override
111         public void surfaceCreated(SurfaceHolder holder) {
112             drawContents();
113         }
114 
115         @Override
116         public void surfaceChanged(
117                 SurfaceHolder holder, int format, int width, int height) {
118             drawContents();
119         }
120         });
121 
122         mSeekBar = (SeekBar) findViewById(R.id.camera_fov_seekBar);
123         mSeekBar.setMax(SEEKBAR_MAX_VALUE);
124         mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
125             @Override
126             public void onStopTrackingTouch(SeekBar seekBar) {}
127 
128             @Override
129             public void onStartTrackingTouch(SeekBar seekBar) {}
130 
131             @Override
132             public void onProgressChanged(
133                     SeekBar seekBar, int progress, boolean fromUser) {
134                 mFovDegrees = seekBarProgressToFovDegrees(progress);
135                 drawContents();
136             }
137         });
138 
139         mDoneButton = (Button) findViewById(R.id.camera_fov_fov_done);
140         mDoneButton.setOnClickListener(new View.OnClickListener() {
141             @Override
142             public void onClick(View v) {
143                 setResult(RESULT_OK);
144                 CtsTestHelper.storeCtsTestResult(DetermineFovActivity.this,
145                         mReportedFovDegrees, mFovDegrees);
146                 finish();
147             }
148         });
149     }
150 
fovToSeekBarProgress(float fovDegrees)151     private int fovToSeekBarProgress(float fovDegrees) {
152         return Math.round((fovDegrees - mFovMinDegrees)
153                 / (mFovMaxDegrees - mFovMinDegrees) * SEEKBAR_MAX_VALUE);
154     }
155 
seekBarProgressToFovDegrees(int progress)156     private float seekBarProgressToFovDegrees(int progress) {
157         float degrees = mFovMinDegrees + (float) progress / SEEKBAR_MAX_VALUE
158                 * (mFovMaxDegrees - mFovMinDegrees);
159         // keep only 2 decimal places.
160         return (int) (degrees * 100) / 100.0f;
161     }
162 
163     @Override
onResume()164     protected void onResume() {
165         super.onResume();
166 
167         setResult(RESULT_CANCELED);
168         mMarkerDistanceCm = getMarkerDistance();
169         mTargetDistanceCm = getTargetDistance();
170         mReportedFovDegrees = PhotoCaptureActivity.getReportedFovDegrees();
171 
172         mFovDegrees = mReportedFovDegrees > 120 ? 60 : mReportedFovDegrees;
173         mFovMaxDegrees = mFovDegrees + FOV_ADJUSTMENT_RANGE / 2;
174         mFovMinDegrees = mFovDegrees - FOV_ADJUSTMENT_RANGE / 2;
175 
176         mMeasuredText = getResources().getString(R.string.camera_fov_displayed_fov_label);
177         mReportedText = getResources().getString(R.string.camera_fov_reported_fov_label);
178 
179         mSeekBar.setProgress(fovToSeekBarProgress(mFovDegrees));
180         drawContents();
181     }
182 
getMarkerDistance()183     private float getMarkerDistance() {
184         // Get the marker distance from the preferences.
185         SharedPreferences prefs =
186                 PreferenceManager.getDefaultSharedPreferences(this);
187         return Float.parseFloat(prefs.getString(
188                 CalibrationPreferenceActivity.OPTION_MARKER_DISTANCE,
189                 DEFAULT_MARKER_DISTANCE));
190     }
191 
getTargetDistance()192     private float getTargetDistance() {
193         // Get the marker distance from the preferences.
194         SharedPreferences prefs =
195                 PreferenceManager.getDefaultSharedPreferences(this);
196         return Float.parseFloat(prefs.getString(
197                 CalibrationPreferenceActivity.OPTION_TARGET_DISTANCE,
198                 DEFAULT_TARGET_DISTANCE));
199     }
200 
focalLengthPixels(float fovDegrees, float imageWidth)201     private float focalLengthPixels(float fovDegrees, float imageWidth) {
202         return (float) (imageWidth
203                 / (2 * Math.tan(fovDegrees / 2 * Math.PI / 180.0f)));
204     }
205 
drawContents()206     private void drawContents() {
207         SurfaceHolder holder = mSurfaceView.getHolder();
208         Canvas canvas = holder.lockCanvas();
209         if (canvas == null || mPhotoBitmap == null) {
210             return;
211         }
212 
213         int canvasWidth = canvas.getWidth();
214         int canvasHeight = canvas.getHeight();
215         int photoWidth = mPhotoBitmap.getWidth();
216         int photoHeight = mPhotoBitmap.getHeight();
217         RectF drawRect = new RectF();
218 
219         // Determine if the canvas aspect ratio is larger than that of the photo.
220         float scale = (float) canvasWidth / photoWidth;
221         int scaledHeight = (int) (scale * photoHeight);
222         if (scaledHeight < canvasHeight) {
223             // If the aspect ratio is smaller, set the destination rectangle to pad
224             // vertically.
225             int pad = (canvasHeight - scaledHeight) / 2;
226             drawRect.set(0, pad, canvasWidth, pad + scaledHeight - 1);
227         } else {
228             // Set the destination rectangle to pad horizontally.
229             scale = (float) canvasHeight / photoHeight;
230             float scaledWidth = scale * photoWidth;
231             float pad = (canvasWidth - scaledWidth) / 2;
232             drawRect.set(pad, 0, pad + scaledWidth - 1, canvasHeight);
233         }
234 
235         // Draw the photo.
236         canvas.drawColor(Color.BLACK);
237         canvas.drawBitmap(mPhotoBitmap, null, drawRect, null);
238 
239         // Draw the fov indicator text.
240         Paint paint = new Paint();
241         paint.setColor(0xffffffff);
242         float textSize = TEXT_SIZE * DetermineFovActivity.this.getResources()
243                 .getDisplayMetrics().scaledDensity;
244         paint.setTextSize(textSize);
245         canvas.drawText(mMeasuredText + " " + mFovDegrees + " degrees.", textSize,
246                 2 * textSize * (1.0f + TEXT_PADDING), paint);
247         canvas.drawText(mReportedText + " " + mReportedFovDegrees + " degrees.",
248                 textSize, textSize * (1.0f + TEXT_PADDING), paint);
249 
250         // Draw the image center circle.
251         paint.setColor(Color.BLACK);
252         paint.setStyle(Paint.Style.STROKE);
253         paint.setStrokeWidth(3);
254         float dstWidth = drawRect.right - drawRect.left + 1;
255         float dstHeight = drawRect.bottom - drawRect.top + 1;
256         float centerX = drawRect.left + dstWidth / 2;
257         canvas.drawLine(centerX, drawRect.top, centerX, drawRect.bottom, paint);
258 
259         // Project the markers into the scaled image with the given field of view.
260         float markerX = mMarkerDistanceCm / 2;
261         float markerZ = mTargetDistanceCm;
262         float focalLength = focalLengthPixels(mFovDegrees, dstWidth);
263         float dx = markerX / markerZ * focalLength;
264         float projectedMarkerLeft = dstWidth / 2 - dx;
265         float projectedMarkerRight = dstWidth / 2 + dx;
266 
267         // Draw the marker lines over the image.
268         paint.setColor(Color.GREEN);
269         paint.setStrokeWidth(2);
270         float markerImageLeft = projectedMarkerLeft + drawRect.left;
271         canvas.drawLine(
272                 markerImageLeft, drawRect.top, markerImageLeft, drawRect.bottom, paint);
273         float markerImageRight = projectedMarkerRight + drawRect.left;
274         canvas.drawLine(markerImageRight, drawRect.top, markerImageRight,
275                         drawRect.bottom, paint);
276 
277         holder.unlockCanvasAndPost(canvas);
278     }
279 }
280