11
22/**
3- * @file Helper module for image processing.
4- *
5- * These functions and classes are only used internally,
3+ * @file Helper module for image processing.
4+ *
5+ * These functions and classes are only used internally,
66 * meaning an end-user shouldn't need to access anything here.
7- *
7+ *
88 * @module utils/image
99 */
1010
11+ import { isNullishDimension } from './core.js' ;
1112import { getFile } from './hub.js' ;
1213import { env } from '../env.js' ;
1314import { Tensor } from './tensor.js' ;
@@ -91,7 +92,7 @@ export class RawImage {
9192 this . channels = channels ;
9293 }
9394
94- /**
95+ /**
9596 * Returns the size of the image (width, height).
9697 * @returns {[number, number] } The size of the image (width, height).
9798 */
@@ -101,9 +102,9 @@ export class RawImage {
101102
102103 /**
103104 * Helper method for reading an image from a variety of input types.
104- * @param {RawImage|string|URL } input
105+ * @param {RawImage|string|URL } input
105106 * @returns The image object.
106- *
107+ *
107108 * **Example:** Read image from a URL.
108109 * ```javascript
109110 * let image = await RawImage.read('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg');
@@ -181,7 +182,7 @@ export class RawImage {
181182
182183 /**
183184 * Helper method to create a new Image from a tensor
184- * @param {Tensor } tensor
185+ * @param {Tensor } tensor
185186 */
186187 static fromTensor ( tensor , channel_format = 'CHW' ) {
187188 if ( tensor . dims . length !== 3 ) {
@@ -306,8 +307,8 @@ export class RawImage {
306307
307308 /**
308309 * Resize the image to the given dimensions. This method uses the canvas API to perform the resizing.
309- * @param {number } width The width of the new image.
310- * @param {number } height The height of the new image.
310+ * @param {number } width The width of the new image. `null` or `-1` will preserve the aspect ratio.
311+ * @param {number } height The height of the new image. `null` or `-1` will preserve the aspect ratio.
311312 * @param {Object } options Additional options for resizing.
312313 * @param {0|1|2|3|4|5|string } [options.resample] The resampling method to use.
313314 * @returns {Promise<RawImage> } `this` to support chaining.
@@ -324,6 +325,20 @@ export class RawImage {
324325 // Ensure resample method is a string
325326 let resampleMethod = RESAMPLING_MAPPING [ resample ] ?? resample ;
326327
328+ // Calculate width / height to maintain aspect ratio, in the event that
329+ // the user passed a null value in.
330+ // This allows users to pass in something like `resize(320, null)` to
331+ // resize to 320 width, but maintain aspect ratio.
332+ const nullish_width = isNullishDimension ( width ) ;
333+ const nullish_height = isNullishDimension ( height ) ;
334+ if ( nullish_width && nullish_height ) {
335+ return this ;
336+ } else if ( nullish_width ) {
337+ width = ( height / this . height ) * this . width ;
338+ } else if ( nullish_height ) {
339+ height = ( width / this . width ) * this . height ;
340+ }
341+
327342 if ( BROWSER_ENV ) {
328343 // TODO use `resample` in browser environment
329344
@@ -360,7 +375,7 @@ export class RawImage {
360375 case 'nearest' :
361376 case 'bilinear' :
362377 case 'bicubic' :
363- // Perform resizing using affine transform.
378+ // Perform resizing using affine transform.
364379 // This matches how the python Pillow library does it.
365380 img = img . affine ( [ width / this . width , 0 , 0 , height / this . height ] , {
366381 interpolator : resampleMethod
@@ -373,7 +388,7 @@ export class RawImage {
373388 img = img . resize ( {
374389 width, height,
375390 fit : 'fill' ,
376- kernel : 'lanczos3' , // PIL Lanczos uses a kernel size of 3
391+ kernel : 'lanczos3' , // PIL Lanczos uses a kernel size of 3
377392 } ) ;
378393 break ;
379394
@@ -452,7 +467,7 @@ export class RawImage {
452467 // Create canvas object for this image
453468 const canvas = this . toCanvas ( ) ;
454469
455- // Create a new canvas of the desired size. This is needed since if the
470+ // Create a new canvas of the desired size. This is needed since if the
456471 // image is too small, we need to pad it with black pixels.
457472 const ctx = createCanvasFunction ( crop_width , crop_height ) . getContext ( '2d' ) ;
458473
@@ -500,7 +515,7 @@ export class RawImage {
500515 // Create canvas object for this image
501516 const canvas = this . toCanvas ( ) ;
502517
503- // Create a new canvas of the desired size. This is needed since if the
518+ // Create a new canvas of the desired size. This is needed since if the
504519 // image is too small, we need to pad it with black pixels.
505520 const ctx = createCanvasFunction ( crop_width , crop_height ) . getContext ( '2d' ) ;
506521
0 commit comments