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 com.avpkit.ferry.JNIReference;
023import com.avpkit.core.IVideoPicture;
024import com.avpkit.core.IPixelFormat;
025
026import java.awt.image.BufferedImage;
027import java.awt.image.DataBufferInt;
028import java.awt.image.DataBufferByte;
029import java.awt.image.DataBuffer;
030import java.awt.image.ColorModel;
031import java.awt.color.ColorSpace;
032import java.awt.image.ComponentColorModel;
033import java.awt.image.SampleModel;
034import java.awt.image.PixelInterleavedSampleModel;
035import java.awt.image.WritableRaster;
036import java.awt.image.Raster;
037import java.nio.ByteBuffer;
038import java.nio.ByteOrder;
039import java.nio.IntBuffer;
040import java.util.concurrent.atomic.AtomicReference;
041
042/** A converter to translate {@link IVideoPicture}s to and from
043 * {@link BufferedImage}s of type {@link BufferedImage#TYPE_3BYTE_BGR}. */
044
045public class BgrConverter extends AConverter
046{
047  // band offsets requried by the sample model
048    
049  private static final int[] mBandOffsets = {2, 1, 0};
050
051  // color space for this converter
052  
053  private static final ColorSpace mColorSpace = 
054    ColorSpace.getInstance(ColorSpace.CS_sRGB);
055
056  /** Construct as converter to translate {@link IVideoPicture}s to and
057   * from {@link BufferedImage}s of type {@link
058   * BufferedImage#TYPE_3BYTE_BGR}.
059   *
060   * @param pictureType the picture type recognized by this converter
061   * @param pictureWidth the width of pictures
062   * @param pictureHeight the height of pictures
063   * @param imageWidth the width of images
064   * @param imageHeight the height of images
065   */
066
067  public BgrConverter(IPixelFormat.Type pictureType, 
068    int pictureWidth, int pictureHeight,
069    int imageWidth, int imageHeight)
070  {
071    super(pictureType, IPixelFormat.Type.BGR24,
072      BufferedImage.TYPE_3BYTE_BGR, pictureWidth, 
073      pictureHeight, imageWidth, imageHeight);
074  }
075
076  /** {@inheritDoc} */
077
078  public IVideoPicture toPicture(BufferedImage image, long timestamp)
079  {
080    // validate the image
081
082    validateImage(image);
083
084    // get the image byte buffer buffer
085
086    DataBuffer imageBuffer = image.getRaster().getDataBuffer();
087    byte[] imageBytes = null;
088    int[] imageInts = null;
089
090    // handle byte buffer case
091
092    if (imageBuffer instanceof DataBufferByte)
093    {
094      imageBytes = ((DataBufferByte)imageBuffer).getData();
095    }
096
097    // handel integer buffer case
098
099    else if (imageBuffer instanceof DataBufferInt)
100    {
101      imageInts = ((DataBufferInt)imageBuffer).getData();
102    }
103
104    // if it's some other type, throw
105
106    else
107    {
108      throw new IllegalArgumentException(
109        "Unsupported BufferedImage data buffer type: " +
110        imageBuffer.getDataType());
111    }
112
113    // create the video picture and get it's underling buffer
114
115    final AtomicReference<JNIReference> ref =
116      new AtomicReference<JNIReference>(null);
117    IVideoPicture resamplePicture = null;
118    try
119    {
120      IVideoPicture picture = IVideoPicture.make(getRequiredPictureType(), image.getWidth(),
121          image.getHeight());
122      ByteBuffer pictureByteBuffer = picture.getByteBuffer(ref);
123
124      if (imageInts != null)
125      {
126        pictureByteBuffer.order(ByteOrder.BIG_ENDIAN);
127        IntBuffer pictureIntBuffer = pictureByteBuffer.asIntBuffer();
128        pictureIntBuffer.put(imageInts);
129      }
130      else
131      {
132        pictureByteBuffer.put(imageBytes);
133      }
134      pictureByteBuffer = null;
135      picture.setComplete(true, getRequiredPictureType(), image.getWidth(),
136          image.getHeight(), timestamp);
137
138      // resample as needed
139      if (willResample())
140      {
141        resamplePicture = picture;
142        picture = resample(resamplePicture, mToPictureResampler);
143      }
144      return picture;
145    }
146    finally
147    {
148      if (resamplePicture != null) resamplePicture.delete();
149      if (ref.get() != null) 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    IVideoPicture resamplePicture = null;
163    AtomicReference<JNIReference> ref = 
164      new AtomicReference<JNIReference>(null);
165    try
166    {
167    if (willResample())
168    {
169      resamplePicture = resample(picture, mToImageResampler);
170      picture = resamplePicture;
171    }
172
173    // get picture parameters
174    
175    final int w = picture.getWidth();
176    final int h = picture.getHeight();
177    
178    // make a copy of the raw bytes int a DataBufferByte which the
179    // writable raster can operate on
180
181    final ByteBuffer byteBuf = picture.getByteBuffer(ref);
182    final byte[] bytes = new byte[picture.getSize()];
183    byteBuf.get(bytes, 0, bytes.length);
184   
185    // create the data buffer from the bytes
186    
187    final DataBufferByte db = new DataBufferByte(bytes, bytes.length);
188    
189    // create an a sample model which matches the byte layout of the
190    // image data and raster which contains the data which now can be
191    // properly interpreted
192    
193    final SampleModel sm = new PixelInterleavedSampleModel(
194      db.getDataType(), w, h, 3, 3 * w, mBandOffsets);
195    final WritableRaster wr = Raster.createWritableRaster(sm, db, null);
196    
197    // create a color model
198    
199    final ColorModel colorModel = new ComponentColorModel(
200      mColorSpace, false, false, ColorModel.OPAQUE, db.getDataType());
201    
202    // return a new image created from the color model and raster
203    
204    return new BufferedImage(colorModel, wr, false, null);
205    }
206    finally
207    {
208      if (resamplePicture!=null)
209        resamplePicture.delete();
210      if (ref.get()!=null)
211        ref.get().delete();
212    }
213  }
214
215  public void delete()
216  {
217    super.close();
218  }
219}