@@ -45,6 +45,7 @@ public abstract class TransformFilter extends AbstractBufferedImageOp {
4545 // Interpolation methods for sampling between pixel centers
4646 public static final int NEAREST_NEIGHBOUR = 0 ;
4747 public static final int BILINEAR = 1 ;
48+ public static final int BICUBIC = 2 ;
4849 protected int interpolation = BILINEAR ;
4950
5051 protected TransformFilter (String filterName ) {
@@ -63,7 +64,7 @@ public void setEdgeAction(int edgeAction) {
6364 /**
6465 * Sets the interpolation method used when sampling between pixel centers.
6566 *
66- * @param interpolation one of NEAREST_NEIGHBOUR or BILINEAR
67+ * @param interpolation one of NEAREST_NEIGHBOUR, BILINEAR, or BICUBIC
6768 */
6869 public void setInterpolation (int interpolation ) {
6970 this .interpolation = interpolation ;
@@ -86,13 +87,14 @@ public BufferedImage filter(BufferedImage src, BufferedImage dst) {
8687 if (dst == null ) {
8788 ColorModel dstCM = src .getColorModel ();
8889 dst = new BufferedImage (dstCM , dstCM .createCompatibleWritableRaster (0 , 0 ), dstCM
89- .isAlphaPremultiplied (), null );
90+ .isAlphaPremultiplied (), null );
9091 }
9192
9293 int [] inPixels = getRGB (src , 0 , 0 , srcWidth , srcHeight , null );
9394
9495 return switch (interpolation ) {
9596 case BILINEAR -> filterPixelsBilinear (dst , srcWidth , srcHeight , inPixels );
97+ case BICUBIC -> filterPixelsBicubic (dst , srcWidth , srcHeight , inPixels );
9698 case NEAREST_NEIGHBOUR -> filterPixelsNN (dst , srcWidth , srcHeight , inPixels );
9799 default -> throw new IllegalStateException ("should not get here" );
98100 };
@@ -190,6 +192,65 @@ private BufferedImage filterPixelsBilinear(BufferedImage dst, int width, int hei
190192 return dst ;
191193 }
192194
195+ /**
196+ * Applies the transform using bicubic interpolation.
197+ */
198+ private BufferedImage filterPixelsBicubic (BufferedImage dst , int width , int height , int [] inPixels ) {
199+ pt = createProgressTracker (height );
200+ @ SuppressWarnings ("unchecked" )
201+ Future <int []>[] rowFutures = new Future [height ];
202+
203+ // process each output line in parallel
204+ for (int y = 0 ; y < height ; y ++) {
205+ float [] out = new float [2 ];
206+ int finalY = y ;
207+
208+ Callable <int []> rowTask = () -> {
209+ int [] outLine = new int [width ];
210+ int [][] p = new int [4 ][4 ];
211+ for (int x = 0 ; x < width ; x ++) {
212+ transformInverse (x , finalY , out );
213+
214+ float srcX_f = out [0 ];
215+ float srcY_f = out [1 ];
216+ int srcX = (int ) FastMath .floor (srcX_f );
217+ int srcY = (int ) FastMath .floor (srcY_f );
218+ float xWeight = srcX_f - srcX ;
219+ float yWeight = srcY_f - srcY ;
220+
221+ // check if the 4x4 neighborhood is completely within the image
222+ if (srcX >= 1 && srcX < srcWidth - 2 && srcY >= 1 && srcY < srcHeight - 2 ) {
223+ // fast path
224+ int i = (srcWidth * (srcY - 1 )) + srcX - 1 ;
225+ for (int row = 0 ; row < 4 ; row ++) {
226+ p [row ][0 ] = inPixels [i ];
227+ p [row ][1 ] = inPixels [i + 1 ];
228+ p [row ][2 ] = inPixels [i + 2 ];
229+ p [row ][3 ] = inPixels [i + 3 ];
230+ i += srcWidth ;
231+ }
232+ } else {
233+ // slow path with edge handling
234+ for (int row = 0 ; row < 4 ; row ++) {
235+ for (int col = 0 ; col < 4 ; col ++) {
236+ p [row ][col ] = sampleBL (inPixels , srcX - 1 + col , srcY - 1 + row , srcWidth , srcHeight );
237+ }
238+ }
239+ }
240+ outLine [x ] = ImageMath .bicubicInterpolate (xWeight , yWeight , p );
241+ }
242+ return outLine ;
243+ };
244+
245+ rowFutures [finalY ] = ThreadPool .submit2 (rowTask );
246+ }
247+
248+ ThreadPool .waitFor2 (rowFutures , dst , width , pt );
249+ finishProgressTracker ();
250+
251+ return dst ;
252+ }
253+
193254 /**
194255 * Samples a pixel for bilinear interpolation.
195256 */
0 commit comments