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.awt.image.BufferedImage;
023import java.awt.image.ColorModel;
024import java.awt.image.DataBuffer;
025import java.awt.image.DataBufferByte;
026import java.awt.image.DataBufferInt;
027import java.awt.image.DirectColorModel;
028import java.awt.image.Raster;
029import java.awt.image.SampleModel;
030import java.awt.image.SinglePixelPackedSampleModel;
031import java.awt.image.WritableRaster;
032import java.nio.ByteBuffer;
033import java.nio.ByteOrder;
034import java.nio.IntBuffer;
035import java.util.concurrent.atomic.AtomicReference;
036
037import com.avpkit.ferry.JNIReference;
038import com.avpkit.core.IPixelFormat;
039import com.avpkit.core.IVideoPicture;
040
041/** A converter to translate {@link IVideoPicture}s to and from
042 * {@link BufferedImage}s of type {@link BufferedImage#TYPE_INT_ARGB}. */
043
044public class ArgbConverter extends AConverter
045{
046  // bit masks requried by the sample model
047    
048  private static final int[] mBitMasks = {0xff0000, 0xff00, 0xff, 0xff000000};
049
050  // the ARGB color model
051
052  private static final ColorModel mColorModel = new DirectColorModel(
053    32, 0xff0000, 0xff00, 0xff, 0xff000000);
054
055  /** Construct as converter to translate {@link IVideoPicture}s to and
056   * from {@link BufferedImage}s of type {@link
057   * BufferedImage#TYPE_INT_ARGB}.
058   *
059   * @param pictureType the picture type recognized by this converter
060   * @param pictureWidth the width of pictures
061   * @param pictureHeight the height of pictures
062   * @param imageWidth the width of images
063   * @param imageHeight the height of images
064   */
065
066  public ArgbConverter(IPixelFormat.Type pictureType, 
067    int pictureWidth, int pictureHeight,
068    int imageWidth, int imageHeight)
069  {
070    super(pictureType, IPixelFormat.Type.ARGB,
071      BufferedImage.TYPE_INT_ARGB, pictureWidth, 
072      pictureHeight, imageWidth, imageHeight);
073  }
074
075  /** {@inheritDoc} */
076
077  public IVideoPicture toPicture(BufferedImage image, long timestamp)
078  {
079    // validate the image
080
081    validateImage(image);
082
083    // get the image byte buffer buffer
084
085    DataBuffer imageBuffer = image.getRaster().getDataBuffer();
086    byte[] imageBytes = null;
087    int[] imageInts = null;
088
089    // handle byte buffer case
090
091    if (imageBuffer instanceof DataBufferByte)
092    {
093      imageBytes = ((DataBufferByte)imageBuffer).getData();
094    }
095
096    // handel integer buffer case
097
098    else if (imageBuffer instanceof DataBufferInt)
099    {
100      imageInts = ((DataBufferInt)imageBuffer).getData();
101    }
102
103    // if it's some other type, throw
104
105    else
106    {
107      throw new IllegalArgumentException(
108        "Unsupported BufferedImage data buffer type: " +
109        imageBuffer.getDataType());
110    }
111
112    // create the video picture and get it's underling buffer
113
114    final AtomicReference<JNIReference> ref = new AtomicReference<JNIReference>(null);
115    IVideoPicture resamplePicture = null;
116    try
117    {
118      IVideoPicture picture = IVideoPicture.make(getRequiredPictureType(), image.getWidth(),
119          image.getHeight());
120
121      ByteBuffer pictureByteBuffer = picture.getByteBuffer(ref);
122
123      if (imageInts != null)
124      {
125        pictureByteBuffer.order(ByteOrder.BIG_ENDIAN);
126        IntBuffer pictureIntBuffer = pictureByteBuffer.asIntBuffer();
127        pictureIntBuffer.put(imageInts);
128      }
129      else
130      {
131        pictureByteBuffer.put(imageBytes);
132      }
133      pictureByteBuffer = null;
134      picture.setComplete(true, getRequiredPictureType(), image.getWidth(),
135          image.getHeight(), timestamp);
136
137      // resample as needed
138      if (willResample()) {
139        resamplePicture = picture;
140        picture = resample(resamplePicture, mToPictureResampler);
141      }
142      return picture;
143    }
144    finally
145    {
146      if (resamplePicture != null)
147        resamplePicture.delete();
148      if (ref.get() != null)
149        ref.get().delete();
150    }
151  }
152
153  /** {@inheritDoc} */
154
155  public BufferedImage toImage(IVideoPicture picture)
156  {
157    // test that the picture is valid
158
159    validatePicture(picture);
160
161    // resample as needed
162
163    IVideoPicture resamplePic = null;
164    final AtomicReference<JNIReference> ref = 
165      new AtomicReference<JNIReference>(null);
166    
167    ByteBuffer byteBuf = null;
168    IntBuffer intBuf = null;
169    int[] ints = null;
170    DataBufferInt db = null;
171    SampleModel sm = null;
172    WritableRaster wr = null;
173    
174    
175    try
176    {
177      if (willResample())
178      {
179        resamplePic = resample(picture, mToImageResampler);
180        picture = resamplePic;
181      }
182      // get picture parameters
183
184      final int w = picture.getWidth();
185      final int h = picture.getHeight();
186
187      // make a copy of the raw bytes in the picture and convert those to
188      // integers
189
190      byteBuf = picture.getByteBuffer(ref);
191
192      // now, for this class of problems, we don't want the code
193      // to switch byte order, so we'll pretend it's in native java order
194
195      byteBuf.order(ByteOrder.BIG_ENDIAN);
196      intBuf = byteBuf.asIntBuffer();
197      ints = new int[picture.getSize() / 4];
198      intBuf.get(ints, 0, ints.length);
199
200      // create the data buffer from the ints
201
202      db = new DataBufferInt(ints, ints.length);
203
204      // create an a sample model which matches the byte layout of the
205      // image data and raster which contains the data which now can be
206      // properly interpreted
207
208      sm = new SinglePixelPackedSampleModel(db.getDataType(),
209          w, h, mBitMasks);
210      wr = Raster.createWritableRaster(sm, db, null);
211
212      // return a new image created from the color model and raster
213
214      return new BufferedImage(mColorModel, wr, false, null);
215    }
216    finally
217    {
218      if (resamplePic != null)
219        resamplePic.delete();
220      if (ref.get() != null)
221        ref.get().delete();
222      
223//      clean this stuff up
224        if (byteBuf != null) {
225          byteBuf.clear();
226          byteBuf = null;
227        }
228        
229        if (intBuf != null) {
230          intBuf.clear();
231          intBuf = null;
232        }
233        
234        if (ints != null) {
235          ints = null;
236        }
237        
238        if (db != null)
239          db = null;
240        
241        if (sm != null)
242          sm = null;
243        
244        if (wr != null)
245          wr = null;
246    }
247  }
248
249  public void delete()
250  {
251    super.close();
252  }
253}