# eBay Image Overlay Feature

## What it is

The overlay feature composites a branded PNG frame on top of product images, producing a square "framed" version suitable for eBay listings. It is separate from the watermark feature (which adds text/logo to images for Shopify). Overlays are stored in their own tables and directory and are not used in any channel sync yet — the eBay integration is the planned destination.

Two visual styles are supported:
- **Framed Plate** — green border + logo band around the image
- **Corner Flag** — top-left badge + gold rule

## Architecture

### Files

```
plugins/webkul/image-watermark/
├── database/migrations/
│   ├── 2026_09_18_000001_create_overlay_configs_table.php
│   └── 2026_09_18_000002_create_overlay_product_images_table.php
├── src/
│   ├── Models/
│   │   ├── OverlayConfig.php
│   │   └── OverlayProductImage.php
│   ├── Services/
│   │   └── OverlayProcessor.php
│   ├── Jobs/
│   │   ├── OverlayProductJob.php    ← processes one product
│   │   └── OverlayBatchJob.php      ← chunks all products, dispatches OverlayProductJob
│   └── Filament/Resources/
│       └── OverlayConfigResource.php  ← Settings → Image Watermark → Overlay Configs
└── resources/views/filament/pages/
    └── manage-overlay-images.blade.php

plugins/webkul/inventories/src/Filament/Clusters/Products/Resources/ProductResource/
└── Pages/ManageOverlayImages.php    ← product sub-tab "Overlays"
```

### DB schema

**`overlay_configs`** — one row per company

| Column | Type | Notes |
|--------|------|-------|
| id | bigIncrements | |
| company_id | FK → companies | unique — one config per company |
| is_enabled | boolean | default false |
| style | string(30) | `framed_plate` or `corner_flag` |
| frame_png_framed_plate | string(500) | storage path to the frame PNG |
| frame_png_corner_flag | string(500) | storage path to the frame PNG |
| output_size | unsignedSmallInt | canvas size in px, default 1600 |
| timestamps | | |

**`overlay_product_images`** — one row per original image per product

| Column | Type | Notes |
|--------|------|-------|
| id | bigIncrements | |
| product_id | FK → products_products | cascade delete |
| overlay_config_id | FK → overlay_configs | cascade delete |
| original_filename | string(500) | path as stored in products_products.images |
| overlaid_filename | string(500) | path to output file in storage |
| config_hash | char(32) | MD5 of style+frame PNGs+output_size — used to detect stale rows |
| status | enum | `done` / `failed` |
| error_message | text | nullable |
| processed_at | timestamp | nullable |
| timestamps | | |

Unique index on `(product_id, overlay_config_id, original_filename)`.

## How the processor works (`OverlayProcessor`)

1. Loads the source image from R2 (or public disk fallback)
2. **Letterboxes** it into a square white canvas of `output_size` pixels (default 1600)
   - Uses `$image->contain($size, $size)` — shrinks to fit, preserves aspect ratio
   - Places centred on white background
3. Calls `applyFrame()`:
   - Loads the active frame PNG from `overlay-frames/` directory in storage
   - Resizes frame to `$size × $size`
   - Composites it over the image at top-left (frame is transparent in the middle, so the product shows through)
4. Saves output to `product_images/overlaid/{config_id}/{basename}` on the same disk
5. Returns the stored path

Library: **Intervention\Image v3** with GD driver.

Preview generation (500px) follows the same steps but returns a base64 data URI instead of saving to disk.

### Stale detection

`OverlayConfig::computeHash()` returns an MD5 of `style + frame_png_framed_plate + frame_png_corner_flag + output_size`. This hash is stored on each `overlay_product_images` row when it is processed. If the config changes later (different style or frame PNG), the hash won't match → the row is shown as "Stale" in the UI, and `OverlayProductJob` with `force=false` will skip images whose hash still matches (avoids redundant reprocessing).

## Admin setup

### Step 1 — run migrations (server only, not done yet)

```bash
php artisan migrate
```

This adds `overlay_configs` and `overlay_product_images` tables. Both migrations are registered in `ImageWatermarkServiceProvider::hasMigrations()`.

### Step 2 — create an Overlay Config

Go to **Settings → Image Watermark → Overlay Configs** → Create.

Fields:
- **Company** — select the company (Somerset4x4 = company_id 2)
- **Enabled** — toggle on
- **Active style** — Framed Plate or Corner Flag
- **Output canvas (px)** — 1600 is standard for eBay
- **Framed Plate — frame PNG** — upload a 1600×1600 transparent PNG (the border/logo frame)
- **Corner Flag — frame PNG** — upload a 1600×1600 transparent PNG (the flag badge)

You only need to upload the PNG for the style you're using. The other can be left blank.

### Frame PNG design requirements

- Size: exactly 1600×1600 (or whatever output_size you set)
- Format: PNG with transparency
- The centre of the image should be transparent so the product shows through
- The border/badge/branding sits around or in the corners of the image
- Stored in `overlay-frames/` directory in R2 / public storage

## Triggering overlays

### Per product — Overlays tab

Open any product → sub-navigation tab **Overlays**.

- Shows before/after grid for all product images
- Status badges: Original / Overlaid / Stale / Failed
- Stale = config changed since last processing (hash mismatch)
- Click **Re-overlay Images** → dispatches `OverlayProductJob` on `high` queue with `force=true`
- Page polls every 3s while a job is in progress (`wire:poll.3s`)

Tab only appears if an enabled OverlayConfig exists for the product's company (checked in `ProductResource::getRecordSubNavigation()`).

### Bulk — all products

Go to **Settings → Image Watermark → Overlay Configs** → Edit the config → header action **Overlay All Products**.

This dispatches `OverlayBatchJob(force: true)` which chunks all products by 100 and dispatches `OverlayProductJob` for each. Jobs run on the `high` queue.

### Auto-trigger on product save

`ImageWatermarkServiceProvider` registers an observer on `Product::saved()` (both base and inventory product models). When a product is saved and has images, `OverlayProductJob` is dispatched automatically with `force=false` — so images whose hash already matches the current config are skipped (only new images are processed).

## Testing checklist

Before testing on the server:

1. **Run migrations** — `php artisan migrate`
2. **Create overlay config** — Settings → Image Watermark → Overlay Configs → Create
3. **Upload a test frame PNG** — any 1600×1600 transparent PNG will work for a test; you can use a simple coloured border PNG
4. **Enable the config** — toggle Enabled on
5. **Open a product** — find a product with images, navigate to the **Overlays** sub-tab
6. **Check the tab appears** — if it doesn't, the OverlayConfig for the company isn't enabled
7. **Click Re-overlay Images** — watch the console for `Overlay · {SKU}` job entry
8. **Refresh the Overlays tab** — overlaid images should appear on the right side of each pair
9. **Change the config** — update the style or output_size, go back to the Overlays tab, rows should show "Stale"
10. **Re-overlay again** — stale images should be reprocessed

### Queue worker

Jobs run on the `high` queue. On the server Supervisor handles this. Locally:

```bash
php -d max_execution_time=0 artisan queue:work --queue=high,default
```

### Checking the job ran

In Cydekick console (top nav job bar) you'll see `Overlay · KVH123456AB` while it's running, then it transitions to `ok` with a detail line like `3/3 overlaid`.

## Output file locations

Overlaid images are stored at:
```
product_images/overlaid/{config_id}/{original_basename}
```

E.g. if original is `jlr-assigned-sku-diagrams/14270/img.png` with config_id=1:
```
product_images/overlaid/1/img.png
```

Frame PNGs uploaded via the form are stored at:
```
overlay-frames/{uploaded_filename}
```

## eBay integration plan (not yet implemented)

The intention is for overlaid images to be used as the images for eBay listings. The plan is:

- `EbayBatchCreateJob` / `EbayBatchSyncJob` check if an OverlayConfig is enabled for the channel's company
- If yes, substitute `overlay_product_images.overlaid_filename` URLs instead of `products_products.images` URLs when building the eBay listing payload
- Same substitution pattern as the Shopify watermark feature (look up by original_filename, swap in overlaid_filename)

This has **not been built yet**. The overlay feature is complete end-to-end (migration, model, processor, jobs, UI), but the eBay listing sync does not yet use overlay images.

## Relationship to watermark feature

The overlay and watermark features are independent:

| | Watermark | Overlay |
|--|--|--|
| Tables | `watermark_configs`, `watermarked_product_images` | `overlay_configs`, `overlay_product_images` |
| Output path | `product_images/watermarked/{config_id}/` | `product_images/overlaid/{config_id}/` |
| Used by | Shopify channel sync | eBay (planned) |
| Effect | Text/logo stamp over image | PNG frame composited at full canvas size |
| Trigger | Batch job + auto on product save | Same pattern |
| Admin | Settings → Image Watermark → Watermark Configs | Settings → Image Watermark → Overlay Configs |
