001/******************************************************************************* 002 * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved. 003 * 004 * This file is part of AVPKit. 005 * 006 * AVPKit is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU Lesser General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * AVPKit is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with AVPKit. If not, see <http://www.gnu.org/licenses/>. 018 *******************************************************************************/ 019 020package com.avpkit.core.video; 021 022import java.util.Map; 023import java.util.HashMap; 024import java.util.Collection; 025import java.util.Collections; 026 027import java.lang.reflect.Constructor; 028import java.lang.reflect.InvocationTargetException; 029 030import java.awt.image.BufferedImage; 031 032import com.avpkit.core.IPixelFormat; 033import com.avpkit.core.IVideoPicture; 034import com.avpkit.core.IVideoResampler; 035 036/** 037 * This factory class creates {@link IConverter} objects for 038 * translation between a 039 * number of {@link IVideoPicture} and {@link BufferedImage} types. Not 040 * all image and picture types are supported. When an unsupported 041 * converter is requested, a descriptive {@link 042 * UnsupportedOperationException} is thrown. 043 * <p> Each converter can 044 * translate between any supported {@link com.avpkit.core.IPixelFormat.Type} 045 * and a single {@link 046 * BufferedImage} type. Converters can optionally resize during the 047 * conversion process. 048 * </p> 049 * <p> 050 * Each converter will re-sample the {@link IVideoPicture} it is converting 051 * to get the data in the same native byte layout as a {@link BufferedImage} 052 * if needed (using {@link IVideoResampler}). This step takes time and creates 053 * a new temporary copy of the image data. To avoid this step 054 * you can make sure the {@link IVideoPicture#getPixelType()} is of the same 055 * binary type as a {@link BufferedImage}. 056 * </p> 057 * <p> Put another way, if you're 058 * converting to {@link BufferedImage#TYPE_3BYTE_BGR}, we will not resample 059 * if the {@link IVideoPicture#getPixelType()} is 060 * {@link com.avpkit.core.IPixelFormat.Type#BGR24}. 061 * </p> 062 */ 063 064public class ConverterFactory 065{ 066 /** 067 * Default constructor 068 */ 069 protected ConverterFactory() { 070 071 } 072 /** Converts between IVideoPictures and {@link BufferedImage} of type 073 * {@link BufferedImage#TYPE_INT_ARGB}. CATION: The ARGB converter 074 * is not currently registered as a converter because the underlying 075 * FFMPEG support for ARGB is unstable. If you really need ARGB 076 * type, manually register the converter, but be aware that the ARGB 077 * images produces may be wrong or broken. 078 */ 079 080 public static final String XUGGLER_ARGB_32 = "XUGGLER-ARGB-32"; 081 082 /** Converts between IVideoPictures and {@link BufferedImage} of type 083 * {@link BufferedImage#TYPE_3BYTE_BGR} */ 084 085 public static final String XUGGLER_BGR_24 = "XUGGLER-BGR-24"; 086 087 // the registered converter types 088 089 private static Map<String, Type> mConverterTypes = new HashMap<String, Type>(); 090 091 // register the known converters 092 093 static 094 { 095 // The ARGB converter is not currently registered as a converter 096 // because the underlying FFMPEG support for ARGB is unstable. If 097 // you really need ARGB type, manually register the converter, but 098 // be aware that the ARGB images produces may be wrong or broken. 099 100 //registerConverter(new Type(XUGGLER_ARGB_32, ArgbConverter.class, 101 //IPixelFormat.Type.ARGB, BufferedImage.TYPE_INT_ARGB)); 102 103 registerConverter(new Type(XUGGLER_BGR_24, BgrConverter.class, 104 IPixelFormat.Type.BGR24, BufferedImage.TYPE_3BYTE_BGR)); 105 } 106 107 /** 108 * Register a converter with this factory. The factory oragnizes the 109 * converters by descriptor, and thus each should be unique unless one 110 * whishes to replace an existing converter. 111 * 112 * @param converterType the type for the converter to be registered 113 * 114 * @return the converter type which this converter displaced, or NULL 115 * of this is a novel converter 116 */ 117 118 public static Type registerConverter(Type converterType) 119 { 120 return mConverterTypes.put(converterType.getDescriptor(), converterType); 121 } 122 123 /** 124 * Unregister a converter with this factory. 125 * 126 * @param converterType the type for the converter to be unregistered 127 * 128 * @return the converter type which removed, or NULL of this converter 129 * was not recognized 130 */ 131 132 public static Type unregisterConverter(Type converterType) 133 { 134 return mConverterTypes.remove(converterType.getDescriptor()); 135 } 136 137 /** 138 * Get a collection of the registered converters. The collection is unmodifiable. 139 * 140 * @return an unmodifiable collection of converter types. 141 */ 142 143 public static Collection<Type> getRegisteredConverters() 144 { 145 return Collections.unmodifiableCollection(mConverterTypes.values()); 146 } 147 148 /** 149 * Find a converter given a type descriptor. 150 * 151 * @param descriptor a unique string which describes this converter 152 * 153 * @return the converter found or NULL if it was not found. 154 */ 155 156 public static Type findRegisteredConverter(String descriptor) 157 { 158 return mConverterTypes.get(descriptor); 159 } 160 161 /** 162 * Find a descriptor given a {@link BufferedImage}. 163 * 164 * @param image a buffered image for which to find a descriptor 165 * 166 * @return the descriptor which matches the image or NULL if it was 167 * not found 168 */ 169 170 public static String findDescriptor(BufferedImage image) 171 { 172 for (Type converterType: getRegisteredConverters()) 173 if (converterType.getImageType() == image.getType()) 174 return converterType.getDescriptor(); 175 176 return null; 177 } 178 179 /** 180 * Create a converter which translates betewen {@link BufferedImage} 181 * and {@link IVideoPicture} types. The {@link 182 * com.avpkit.core.IPixelFormat.Type} and size are extracted from 183 * the passed in picture. This factory will attempt to create a 184 * converter which can perform the translation. If no converter can 185 * be created, a descriptive {@link UnsupportedOperationException} is 186 * thrown. 187 * 188 * @param converterDescriptor the unique string descriptor of the 189 * converter which is to be created 190 * @param picture the picture from which size and type are extracted 191 * 192 * @throws UnsupportedOperationException if the converter can not be 193 * found 194 * @throws UnsupportedOperationException if the found converter can 195 * not be properly initialized 196 * @throws IllegalArgumentException if the passed {@link 197 * IVideoPicture} is NULL; 198 */ 199 200 public static IConverter createConverter( 201 String converterDescriptor, 202 IVideoPicture picture) 203 { 204 if (picture == null) 205 throw new IllegalArgumentException("The picture is NULL."); 206 207 return createConverter(converterDescriptor, picture.getPixelType(), 208 picture.getWidth(), picture.getHeight()); 209 } 210 211 /** 212 * Create a converter which translates betewen {@link BufferedImage} 213 * and {@link IVideoPicture} types. The {@link BufferedImage} type and 214 * size are extracted from the passed in image. This factory will 215 * attempt to create a converter which can perform the translation. 216 * If no converter can be created, a descriptive {@link 217 * UnsupportedOperationException} is thrown. 218 * 219 * @param image the image from which size and type are extracted 220 * @param pictureType the picture type of the converter 221 * 222 * @throws UnsupportedOperationException if no converter for the 223 * specifed BufferedImage type exists 224 * @throws UnsupportedOperationException if the found converter can 225 * not be properly initialized 226 * @throws IllegalArgumentException if the passed {@link 227 * BufferedImage} is NULL; 228 */ 229 230 public static IConverter createConverter( 231 BufferedImage image, 232 IPixelFormat.Type pictureType) 233 { 234 if (image == null) 235 throw new IllegalArgumentException("The image is NULL."); 236 237 // find the converter type based in image type 238 239 String converterDescriptor = findDescriptor(image); 240 if (converterDescriptor == null) 241 throw new UnsupportedOperationException( 242 "No converter found for BufferedImage type #" + 243 image.getType()); 244 245 // create and return the converter 246 247 return createConverter(converterDescriptor, pictureType, 248 image.getWidth(), image.getHeight()); 249 } 250 251 /** 252 * Create a converter which translates betewen {@link BufferedImage} 253 * and {@link IVideoPicture} types. This factory will attempt to 254 * create a converter which can perform the translation. If no 255 * converter can be created, a descriptive {@link 256 * UnsupportedOperationException} is thrown. 257 * 258 * @param converterDescriptor the unique string descriptor of the 259 * converter which is to be created 260 * @param pictureType the picture type of the converter 261 * @param width the width of pictures and images 262 * @param height the height of pictures and images 263 * 264 * @throws UnsupportedOperationException if the converter can not be 265 * found 266 * @throws UnsupportedOperationException if the found converter can 267 * not be properly initialized 268 */ 269 270 public static IConverter createConverter( 271 String converterDescriptor, 272 IPixelFormat.Type pictureType, 273 int width, int height) 274 { 275 return createConverter(converterDescriptor, pictureType, 276 width, height, width, height); 277 } 278 279 /** 280 * Create a converter which translates betewen {@link BufferedImage} 281 * and {@link IVideoPicture} types. This factory will attempt to 282 * create a converter which can perform the translation. If different 283 * image and pictures sizes are passed the converter will resize 284 * during translation. If no converter can be created, a descriptive 285 * {@link UnsupportedOperationException} is thrown. 286 * 287 * @param converterDescriptor the unique string descriptor of the 288 * converter which is to be created 289 * @param pictureType the picture type of the converter 290 * @param pictureWidth the width of pictures 291 * @param pictureHeight the height of pictures 292 * @param imageWidth the width of images 293 * @param imageHeight the height of images 294 * 295 * @throws UnsupportedOperationException if the converter can not be 296 * found 297 * @throws UnsupportedOperationException if the converter can not be 298 * properly created or initialized 299 */ 300 301 public static IConverter createConverter( 302 String converterDescriptor, 303 IPixelFormat.Type pictureType, 304 int pictureWidth, int pictureHeight, 305 int imageWidth, int imageHeight) 306 { 307 IConverter converter = null; 308 309 // establish the converter type 310 311 Type converterType = findRegisteredConverter(converterDescriptor); 312 if (null == converterType) 313 throw new UnsupportedOperationException( 314 "No converter \"" + converterDescriptor + "\" found."); 315 316 // create the converter 317 318 try 319 { 320 // establish the constructor 321 322 Constructor<? extends IConverter> converterConstructor = 323 converterType.getConverterClass().getConstructor(IPixelFormat.Type.class, 324 int.class, int.class, int.class, int.class); 325 326 // create the converter 327 328 converter = converterConstructor.newInstance( 329 pictureType, pictureWidth, pictureHeight, imageWidth, imageHeight); 330 } 331 catch (NoSuchMethodException e) 332 { 333 throw new UnsupportedOperationException( 334 "Converter " + converterType.getConverterClass() + 335 " requries a constructor of the form " + 336 "(IPixelFormat.Type, int, int, int, int)"); 337 } 338 catch (InvocationTargetException e) 339 { 340 Throwable cause = e.getCause(); 341 if (cause != null && cause instanceof OutOfMemoryError) 342 { 343 throw (OutOfMemoryError)cause; 344 } 345 else 346 { 347 throw new UnsupportedOperationException( 348 "Converter " + converterType.getConverterClass() + 349 " constructor failed with: " + e.getCause()); 350 } 351 } 352 catch (IllegalAccessException e) 353 { 354 Throwable cause = e.getCause(); 355 if (cause != null && cause instanceof OutOfMemoryError) 356 { 357 throw (OutOfMemoryError)cause; 358 } 359 else 360 { 361 throw new UnsupportedOperationException( 362 "Converter " + converterType.getConverterClass() + 363 " constructor failed with: " + e.getCause()); 364 } 365 } 366 catch (InstantiationException e) 367 { 368 Throwable cause = e.getCause(); 369 if (cause != null && cause instanceof OutOfMemoryError) 370 { 371 throw (OutOfMemoryError)cause; 372 } 373 else 374 { 375 throw new UnsupportedOperationException( 376 "Converter " + converterType.getConverterClass() + 377 " constructor failed with: " + e.getCause()); 378 } 379 } 380 381 // return the newly created converter 382 383 return converter; 384 } 385 386 387 /** 388 * This class describes a converter type and is used to register and 389 * unregister types with {@link ConverterFactory}. The factory 390 * oragnizes the converters by descriptor, and thus each should be 391 * unique unless you wish to replace an existing converter. 392 */ 393 394 public static class Type 395 { 396 /** The unique string which describes this converter. */ 397 398 final private String mDescriptor; 399 400 /** The class responsible for converting between types. */ 401 402 final private Class<? extends IConverter> mConverterClass; 403 404 /** 405 * The {@link com.avpkit.core.IPixelFormat.Type} which the 406 * picture must be in to convert it to a {@link BufferedImage} 407 */ 408 409 final private IPixelFormat.Type mPictureType; 410 411 /** 412 * The {@link BufferedImage} type which the image must be in to 413 * convert it to a {@link com.avpkit.core.IVideoPicture} 414 */ 415 416 final private int mImageType; 417 418 /** Construct a complete converter type description. 419 * 420 * @param descriptor a unique string which describes this converter 421 * @param converterClass the class which converts between pictures 422 * and images 423 * @param pictureType the {@link 424 * com.avpkit.core.IPixelFormat.Type} type which the 425 * picture must be in to convert it to an image 426 * @param imageType the {@link BufferedImage} type which the picture 427 * must be in to convert it to a {@link BufferedImage} 428 */ 429 430 public Type(String descriptor, Class<? extends IConverter> converterClass, 431 IPixelFormat.Type pictureType, int imageType) 432 { 433 mDescriptor = descriptor; 434 mConverterClass = converterClass; 435 mPictureType = pictureType; 436 mImageType = imageType; 437 } 438 439 /** Get the unique string which describes this converter. */ 440 441 public String getDescriptor() 442 { 443 return mDescriptor; 444 } 445 446 /** Get the class responsible for converting between types. */ 447 448 public Class<? extends IConverter> getConverterClass() 449 { 450 return mConverterClass; 451 } 452 453 /** 454 * Get the {@link com.avpkit.core.IPixelFormat.Type} which the 455 * picture must be in to convert it to a {@link BufferedImage} 456 */ 457 458 public IPixelFormat.Type getPictureType() 459 { 460 return mPictureType; 461 } 462 463 /** 464 * Get the {@link BufferedImage} type which the image must be in to 465 * convert it to a {@link com.avpkit.core.IVideoPicture} 466 */ 467 468 public int getImageType() 469 { 470 return mImageType; 471 } 472 473 /** 474 * Get a string description of this conveter type. 475 */ 476 477 public String toString() 478 { 479 480 return getDescriptor() + ": picture type " + getPictureType() + 481 ", image type " + getImageType(); 482 } 483 } 484 485 /** 486 * Convert a {@link BufferedImage} of any type, to {@link 487 * BufferedImage} of a specified type. If the source image is the 488 * same type as the target type, then original image is returned, 489 * otherwise new image of the correct type is created and the content 490 * of the source image is copied into the new image. 491 * 492 * @param sourceImage the image to be converted 493 * @param targetType the desired BufferedImage type 494 * 495 * @return a BufferedImage of the specifed target type. 496 * 497 * @see BufferedImage 498 */ 499 500 public static BufferedImage convertToType(BufferedImage sourceImage, 501 int targetType) 502 { 503 BufferedImage image; 504 505 // if the source image is already the target type, return the source image 506 507 if (sourceImage.getType() == targetType) 508 image = sourceImage; 509 510 // otherwise create a new image of the target type and draw the new 511 // image 512 513 else 514 { 515 image = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), 516 targetType); 517 image.getGraphics().drawImage(sourceImage, 0, 0, null); 518 } 519 520 return image; 521 } 522}