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