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