1 /*
2  * Copyright (C) 2019 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.bluetooth.avrcpcontroller;
18 
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21 
22 /**
23  * The pixel size or range of pixel sizes in which the image is available
24  *
25  * <p>A FIXED size is represented as the following, where W is width and H is height. The domain of
26  * values is [0, 65535]
27  *
28  * <p>W*H
29  *
30  * <p>A RESIZABLE size that allows a modified aspect ratio is represented as the following, where
31  * W_1*H_1 is the minimum width and height pair and W2*H2 is the maximum width and height pair. The
32  * domain of values is [0, 65535]
33  *
34  * <p>W_1*H_1-W2*H2
35  *
36  * <p>A RESIZABLE size that allows a fixed aspect ratio is represented as the following, where W_1
37  * is the minimum width and W2*H2 is the maximum width and height pair. The domain of values is [0,
38  * 65535]
39  *
40  * <p>W_1**-W2*H2
41  *
42  * <p>For each possible intermediate width value, the corresponding height is calculated using the
43  * formula
44  *
45  * <p>H=(W*H2)/W2
46  */
47 public class BipPixel {
48     private static final String TAG = "avrcpcontroller.BipPixel";
49 
50     // The BIP specification declares this as the max size to be transferred. You can optionally
51     // use this value to indicate there is no upper bound on pixel size.
52     public static final int PIXEL_MAX = 65535;
53 
54     // Note that the integer values also map to the number of '*' delimiters that exist in each
55     // formatted string
56     public static final int TYPE_UNKNOWN = 0;
57     public static final int TYPE_FIXED = 1;
58     public static final int TYPE_RESIZE_MODIFIED_ASPECT_RATIO = 2;
59     public static final int TYPE_RESIZE_FIXED_ASPECT_RATIO = 3;
60 
61     private final int mType;
62     private final int mMinWidth;
63     private final int mMinHeight;
64     private final int mMaxWidth;
65     private final int mMaxHeight;
66 
67     /** Create a fixed size BipPixel object */
createFixed(int width, int height)68     public static BipPixel createFixed(int width, int height) {
69         return new BipPixel(TYPE_FIXED, width, height, width, height);
70     }
71 
72     /** Create a resizable modifiable aspect ratio BipPixel object */
createResizableModified( int minWidth, int minHeight, int maxWidth, int maxHeight)73     public static BipPixel createResizableModified(
74             int minWidth, int minHeight, int maxWidth, int maxHeight) {
75         return new BipPixel(
76                 TYPE_RESIZE_MODIFIED_ASPECT_RATIO, minWidth, minHeight, maxWidth, maxHeight);
77     }
78 
79     /** Create a resizable fixed aspect ratio BipPixel object */
createResizableFixed(int minWidth, int maxWidth, int maxHeight)80     public static BipPixel createResizableFixed(int minWidth, int maxWidth, int maxHeight) {
81         int minHeight = (minWidth * maxHeight) / maxWidth;
82         return new BipPixel(
83                 TYPE_RESIZE_FIXED_ASPECT_RATIO, minWidth, minHeight, maxWidth, maxHeight);
84     }
85 
86     /**
87      * Directly create a BipPixel object knowing your exact type and dimensions. Internal use only
88      */
BipPixel(int type, int minWidth, int minHeight, int maxWidth, int maxHeight)89     private BipPixel(int type, int minWidth, int minHeight, int maxWidth, int maxHeight) {
90         if (isDimensionInvalid(minWidth)
91                 || isDimensionInvalid(maxWidth)
92                 || isDimensionInvalid(minHeight)
93                 || isDimensionInvalid(maxHeight)) {
94             throw new IllegalArgumentException("Dimension's must be in [0, " + PIXEL_MAX + "]");
95         }
96 
97         mType = type;
98         mMinWidth = minWidth;
99         mMinHeight = minHeight;
100         mMaxWidth = maxWidth;
101         mMaxHeight = maxHeight;
102     }
103 
104     /** Create a BipPixel object from an Image Format pixel attribute string */
BipPixel(String pixel)105     public BipPixel(String pixel) {
106         int type = TYPE_UNKNOWN;
107         int minWidth = -1;
108         int minHeight = -1;
109         int maxWidth = -1;
110         int maxHeight = -1;
111 
112         int typeHint = determinePixelType(pixel);
113         switch (typeHint) {
114             case TYPE_FIXED:
115                 Pattern fixed = Pattern.compile("^(\\d{1,5})\\*(\\d{1,5})$");
116                 Matcher m1 = fixed.matcher(pixel);
117                 if (m1.matches()) {
118                     type = TYPE_FIXED;
119                     minWidth = Integer.parseInt(m1.group(1));
120                     maxWidth = Integer.parseInt(m1.group(1));
121                     minHeight = Integer.parseInt(m1.group(2));
122                     maxHeight = Integer.parseInt(m1.group(2));
123                 }
124                 break;
125             case TYPE_RESIZE_MODIFIED_ASPECT_RATIO:
126                 Pattern modifiedRatio =
127                         Pattern.compile("^(\\d{1,5})\\*(\\d{1,5})-(\\d{1,5})\\*(\\d{1,5})$");
128                 Matcher m2 = modifiedRatio.matcher(pixel);
129                 if (m2.matches()) {
130                     type = TYPE_RESIZE_MODIFIED_ASPECT_RATIO;
131                     minWidth = Integer.parseInt(m2.group(1));
132                     minHeight = Integer.parseInt(m2.group(2));
133                     maxWidth = Integer.parseInt(m2.group(3));
134                     maxHeight = Integer.parseInt(m2.group(4));
135                 }
136                 break;
137             case TYPE_RESIZE_FIXED_ASPECT_RATIO:
138                 Pattern fixedRatio = Pattern.compile("^(\\d{1,5})\\*\\*-(\\d{1,5})\\*(\\d{1,5})$");
139                 Matcher m3 = fixedRatio.matcher(pixel);
140                 if (m3.matches()) {
141                     type = TYPE_RESIZE_FIXED_ASPECT_RATIO;
142                     minWidth = Integer.parseInt(m3.group(1));
143                     maxWidth = Integer.parseInt(m3.group(2));
144                     maxHeight = Integer.parseInt(m3.group(3));
145                     minHeight = (minWidth * maxHeight) / maxWidth;
146                 }
147                 break;
148             default:
149                 break;
150         }
151         if (type == TYPE_UNKNOWN) {
152             throw new ParseException("Failed to determine type of '" + pixel + "'");
153         }
154         if (isDimensionInvalid(minWidth)
155                 || isDimensionInvalid(maxWidth)
156                 || isDimensionInvalid(minHeight)
157                 || isDimensionInvalid(maxHeight)) {
158             throw new ParseException("Parsed dimensions must be in [0, " + PIXEL_MAX + "]");
159         }
160 
161         mType = type;
162         mMinWidth = minWidth;
163         mMinHeight = minHeight;
164         mMaxWidth = maxWidth;
165         mMaxHeight = maxHeight;
166     }
167 
getType()168     public int getType() {
169         return mType;
170     }
171 
getMinWidth()172     public int getMinWidth() {
173         return mMinWidth;
174     }
175 
getMaxWidth()176     public int getMaxWidth() {
177         return mMaxWidth;
178     }
179 
getMinHeight()180     public int getMinHeight() {
181         return mMinHeight;
182     }
183 
getMaxHeight()184     public int getMaxHeight() {
185         return mMaxHeight;
186     }
187 
188     /**
189      * Determines the type of the pixel string by counting the number of '*' delimiters in the
190      * string.
191      *
192      * <p>Note that the overall maximum size of any pixel string is 23 characters in length due to
193      * the max size of each dimension
194      *
195      * @return The corresponding type we should assume the given pixel string is
196      */
determinePixelType(String pixel)197     private static int determinePixelType(String pixel) {
198         if (pixel == null || pixel.length() > 23) return TYPE_UNKNOWN;
199         int delimCount = 0;
200         for (char c : pixel.toCharArray()) {
201             if (c == '*') delimCount++;
202         }
203         return delimCount > 0 && delimCount <= 3 ? delimCount : TYPE_UNKNOWN;
204     }
205 
isDimensionInvalid(int dimension)206     protected static boolean isDimensionInvalid(int dimension) {
207         return dimension < 0 || dimension > PIXEL_MAX;
208     }
209 
210     @Override
equals(Object o)211     public boolean equals(Object o) {
212         if (o == this) return true;
213         if (!(o instanceof BipPixel)) return false;
214 
215         BipPixel p = (BipPixel) o;
216         return p.getType() == getType()
217                 && p.getMinWidth() == getMinWidth()
218                 && p.getMaxWidth() == getMaxWidth()
219                 && p.getMinHeight() == getMinHeight()
220                 && p.getMaxHeight() == getMaxHeight();
221     }
222 
223     @Override
toString()224     public String toString() {
225         String s = null;
226         switch (mType) {
227             case TYPE_FIXED:
228                 s = mMaxWidth + "*" + mMaxHeight;
229                 break;
230             case TYPE_RESIZE_MODIFIED_ASPECT_RATIO:
231                 s = mMinWidth + "*" + mMinHeight + "-" + mMaxWidth + "*" + mMaxHeight;
232                 break;
233             case TYPE_RESIZE_FIXED_ASPECT_RATIO:
234                 s = mMinWidth + "**-" + mMaxWidth + "*" + mMaxHeight;
235                 break;
236         }
237         return s;
238     }
239 }
240