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}