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}