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}