From 7abfce4634e66cf60c87946265fa023dff7d1d10 Mon Sep 17 00:00:00 2001
From: egor-romanov <egor.romanov@gmail.com>
Date: Mon, 4 Dec 2023 22:17:18 +0400
Subject: [PATCH 1/4] feat: add gravity param for image transformation

---
 src/http/schemas/transformations.ts |  3 +++
 src/storage/renderer/image.ts       | 33 +++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/src/http/schemas/transformations.ts b/src/http/schemas/transformations.ts
index 907dd154..2932d59c 100644
--- a/src/http/schemas/transformations.ts
+++ b/src/http/schemas/transformations.ts
@@ -4,4 +4,7 @@ export const transformationOptionsSchema = {
   resize: { type: 'string', enum: ['cover', 'contain', 'fill'] },
   format: { type: 'string', enum: ['origin', 'avif'] },
   quality: { type: 'integer', minimum: 20, maximum: 100 },
+  gravity: { type: 'string', enum: ['no', 'so', 'ea', 'we', 'noea', 'nowe', 'soea', 'sowe', 'ce', 'sm', 'fp'] },
+  x_offset: { type: 'number', examples: [0.1, 100], minimum: 0 },
+  y_offset: { type: 'number', examples: [0.1, 100], minimum: 0 },
 } as const
diff --git a/src/storage/renderer/image.ts b/src/storage/renderer/image.ts
index d09bdcdf..e41b9011 100644
--- a/src/storage/renderer/image.ts
+++ b/src/storage/renderer/image.ts
@@ -17,6 +17,9 @@ export interface TransformOptions {
   resize?: 'cover' | 'contain' | 'fill'
   format?: 'origin' | 'avif'
   quality?: number
+  gravity?: 'no' | 'so' | 'ea' | 'we' | 'noea' | 'nowe' | 'soea' | 'sowe' | 'ce' | 'sm' | 'fp'
+  x_offset?: number
+  y_offset?: number
 }
 
 const {
@@ -115,6 +118,27 @@ export class ImageRenderer extends Renderer {
       segments.push(`format:${options.format}`)
     }
 
+    if (options.gravity) {
+      switch (options.gravity) {
+        case 'sm': {
+          segments.push(`gravity:${options.gravity}`)
+          break
+        }
+        case 'fp': {
+          if (!(options.x_offset && options.y_offset && 
+                options.x_offset <= 1 && options.y_offset <= 1 && 
+                options.x_offset >= -1 && options.y_offset >= -1)) {
+            throw new StorageBackendError('Invalid focus point', 400, 'Focal point requires x and y coordinates within 0-1 range')
+          }
+          segments.push(`gravity:${options.gravity}:${options.x_offset}:${options.y_offset}`)
+          break
+        }
+        default: {
+          segments.push(`gravity:${options.gravity}:${options.x_offset ?? 0}:${options.y_offset ?? 0}`)
+        }
+      }
+    }
+
     return segments
   }
 
@@ -170,6 +194,15 @@ export class ImageRenderer extends Renderer {
         case 'quality':
           all.quality = parseInt(value, 10)
           break
+        case 'gravity':
+          all.gravity = value
+          break
+        case 'x_offset':
+          all.x_offset = parseFloat(value)
+          break
+        case 'y_offset':
+          all.y_offset = parseFloat(value)
+          break
       }
       return all
     }, {} as TransformOptions)

From 29b541ecbc13ed0ac5e929576b3c7b9ec52e97e6 Mon Sep 17 00:00:00 2001
From: egor-romanov <egor.romanov@gmail.com>
Date: Mon, 4 Dec 2023 22:26:18 +0400
Subject: [PATCH 2/4] fix: offset can be negative

---
 src/http/schemas/transformations.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/http/schemas/transformations.ts b/src/http/schemas/transformations.ts
index 2932d59c..c6ca3568 100644
--- a/src/http/schemas/transformations.ts
+++ b/src/http/schemas/transformations.ts
@@ -5,6 +5,6 @@ export const transformationOptionsSchema = {
   format: { type: 'string', enum: ['origin', 'avif'] },
   quality: { type: 'integer', minimum: 20, maximum: 100 },
   gravity: { type: 'string', enum: ['no', 'so', 'ea', 'we', 'noea', 'nowe', 'soea', 'sowe', 'ce', 'sm', 'fp'] },
-  x_offset: { type: 'number', examples: [0.1, 100], minimum: 0 },
-  y_offset: { type: 'number', examples: [0.1, 100], minimum: 0 },
+  x_offset: { type: 'number', examples: [0.1, 100] },
+  y_offset: { type: 'number', examples: [0.1, -100] },
 } as const

From 65fa2dd6901c3a11177478e7e9a7bc49ed4474b2 Mon Sep 17 00:00:00 2001
From: egor-romanov <egor.romanov@gmail.com>
Date: Mon, 4 Dec 2023 22:38:18 +0400
Subject: [PATCH 3/4] fix: prettier

---
 src/http/schemas/transformations.ts |  5 ++++-
 src/storage/renderer/image.ts       | 23 ++++++++++++++++++-----
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/http/schemas/transformations.ts b/src/http/schemas/transformations.ts
index c6ca3568..2333c650 100644
--- a/src/http/schemas/transformations.ts
+++ b/src/http/schemas/transformations.ts
@@ -4,7 +4,10 @@ export const transformationOptionsSchema = {
   resize: { type: 'string', enum: ['cover', 'contain', 'fill'] },
   format: { type: 'string', enum: ['origin', 'avif'] },
   quality: { type: 'integer', minimum: 20, maximum: 100 },
-  gravity: { type: 'string', enum: ['no', 'so', 'ea', 'we', 'noea', 'nowe', 'soea', 'sowe', 'ce', 'sm', 'fp'] },
+  gravity: {
+    type: 'string',
+    enum: ['no', 'so', 'ea', 'we', 'noea', 'nowe', 'soea', 'sowe', 'ce', 'sm', 'fp'],
+  },
   x_offset: { type: 'number', examples: [0.1, 100] },
   y_offset: { type: 'number', examples: [0.1, -100] },
 } as const
diff --git a/src/storage/renderer/image.ts b/src/storage/renderer/image.ts
index e41b9011..26824a4d 100644
--- a/src/storage/renderer/image.ts
+++ b/src/storage/renderer/image.ts
@@ -125,16 +125,29 @@ export class ImageRenderer extends Renderer {
           break
         }
         case 'fp': {
-          if (!(options.x_offset && options.y_offset && 
-                options.x_offset <= 1 && options.y_offset <= 1 && 
-                options.x_offset >= -1 && options.y_offset >= -1)) {
-            throw new StorageBackendError('Invalid focus point', 400, 'Focal point requires x and y coordinates within 0-1 range')
+          if (
+            !(
+              options.x_offset &&
+              options.y_offset &&
+              options.x_offset <= 1 &&
+              options.y_offset <= 1 &&
+              options.x_offset >= -1 &&
+              options.y_offset >= -1
+            )
+          ) {
+            throw new StorageBackendError(
+              'Invalid focus point',
+              400,
+              'Focal point requires x and y coordinates within 0-1 range'
+            )
           }
           segments.push(`gravity:${options.gravity}:${options.x_offset}:${options.y_offset}`)
           break
         }
         default: {
-          segments.push(`gravity:${options.gravity}:${options.x_offset ?? 0}:${options.y_offset ?? 0}`)
+          segments.push(
+            `gravity:${options.gravity}:${options.x_offset ?? 0}:${options.y_offset ?? 0}`
+          )
         }
       }
     }

From 63ed13f8e47c33418878940398a6116e5201b0da Mon Sep 17 00:00:00 2001
From: egor-romanov <egor.romanov@gmail.com>
Date: Tue, 5 Dec 2023 19:40:14 +0400
Subject: [PATCH 4/4] fix: fp x and y boundaries

---
 src/storage/renderer/image.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/storage/renderer/image.ts b/src/storage/renderer/image.ts
index 26824a4d..985dd101 100644
--- a/src/storage/renderer/image.ts
+++ b/src/storage/renderer/image.ts
@@ -131,8 +131,8 @@ export class ImageRenderer extends Renderer {
               options.y_offset &&
               options.x_offset <= 1 &&
               options.y_offset <= 1 &&
-              options.x_offset >= -1 &&
-              options.y_offset >= -1
+              options.x_offset >= 0 &&
+              options.y_offset >= 0
             )
           ) {
             throw new StorageBackendError(