PHP code example of zielu92 / filament-image-labeler

1. Go to this page and download the library: Download zielu92/filament-image-labeler library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

zielu92 / filament-image-labeler example snippets


use Zielu92\FilamentImageLabeler\Concerns\HasAnnotations;

class Photo extends Model
{
    use HasAnnotations;
}

use Zielu92\FilamentImageLabeler\Forms\Components\ImageLabel;

ImageLabel::make('annotations')
    ->image(fn ($record) => $record?->getFirstMediaUrl())
    ->enableSquare()      // Enable rectangle drawing
    ->enablePolygon()     // Enable polygon drawing
    ->enableClear()       // Show "Clear All" button
    ->multiple()          // Allow multiple annotations (default: true)
    ->live()
    ->columnSpanFull()

// In your CreateRecord page:
class CreatePhoto extends CreateRecord
{
    private array $annotationData = [];

    protected function mutateFormDataBeforeCreate(array $data): array
    {
        // Extract annotation data before Eloquent save
        $this->annotationData = collect($data['annotation_repeater'] ?? [])
            ->map(fn ($item) => [
                'annotation_id' => $item['annotation_id'],
                'geometry' => $item['geometry'],
                'metadata' => [
                    'title' => $item['title'] ?? null,
                    'category' => $item['category'] ?? null,
                ],
            ])->toArray();

        unset($data['annotations'], $data['annotation_repeater']);

        return $data;
    }

    protected function afterCreate(): void
    {
        $this->record->syncAnnotations($this->annotationData);
    }
}

// In your EditRecord page:
class EditPhoto extends EditRecord
{
    private array $annotationData = [];

    protected function mutateFormDataBeforeFill(array $data): array
    {
        $annotations = $this->record->annotations()->orderBy('id')->get();

        if ($annotations->isNotEmpty()) {
            // Populate the repeater with your app's metadata fields
            $data['annotation_repeater'] = $annotations->map(fn ($ann) => [
                'annotation_id' => $ann->annotation_id,
                'title' => $ann->metadata['title'] ?? '',
                'category' => $ann->metadata['category'] ?? null,
                'geometry' => json_encode($ann->geometry),
            ])->toArray();

            // Populate the canvas with geometry
            $data['annotations'] = $annotations->map(fn ($ann) => [
                'id' => $ann->annotation_id,
                'target' => $ann->geometry,
            ])->toArray();
        }

        return $data;
    }

    protected function mutateFormDataBeforeSave(array $data): array
    {
        $this->annotationData = collect($data['annotation_repeater'] ?? [])
            ->map(fn ($item) => [
                'annotation_id' => $item['annotation_id'],
                'geometry' => $item['geometry'],
                'metadata' => [
                    'title' => $item['title'] ?? null,
                    'category' => $item['category'] ?? null,
                ],
            ])->toArray();

        unset($data['annotations'], $data['annotation_repeater']);

        return $data;
    }

    protected function afterSave(): void
    {
        $this->record->syncAnnotations($this->annotationData);
    }
}

use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Support\HtmlString;
use Zielu92\FilamentImageLabeler\Forms\Components\ImageLabel;
use Zielu92\FilamentImageLabeler\Support\AnnotationColor;

// Define your palette once — pass the same array to both the component and AnnotationColor
$palette = [
    '#ef4444', '#3b82f6', '#10b981', '#f59e0b',
    '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16',
    '#f97316', '#6366f1', '#14b8a6', '#e11d48',
];

// The annotation canvas with colored annotations
ImageLabel::make('annotations')
    ->image(fn ($record) => $record?->getFirstMediaUrl())
    ->coloredAnnotations($palette)
    ->enableSquare()
    ->enablePolygon()
    ->enableClear()
    ->multiple()
    ->live()
    ->columnSpanFull()
    ->afterStateUpdated(function (?array $state, Set $set, Get $get) {
        $currentRepeater = $get('annotation_repeater') ?? [];
        $existingById = collect($currentRepeater)->keyBy('annotation_id');

        $newRepeater = collect($state ?? [])->map(function ($annotation) use ($existingById) {
            $id = $annotation['id'];
            $existing = $existingById->get($id);

            return [
                'annotation_id' => $id,
                'title' => $existing['title'] ?? '',
                'category' => $existing['category'] ?? null,
                'geometry' => json_encode($annotation['target'] ?? []),
            ];
        })->toArray();

        $set('annotation_repeater', $newRepeater);
    }),

// The metadata repeater with color swatch
Repeater::make('annotation_repeater')
    ->schema([
        Placeholder::make('color_swatch')
            ->hiddenLabel()
            ->content(function (Get $get) use ($palette): HtmlString {
                $id = $get('annotation_id') ?? '';
                $color = AnnotationColor::forId($id, $palette);

                return new HtmlString(
                    '<div style="width: 24px; height: 24px; border-radius: 4px; '
                    . 'background-color: ' . $color . '; '
                    . 'border: 1px solid rgba(0,0,0,0.2);"></div>'
                );
            })
            ->columnSpan(1),
        TextInput::make('title')
            ->label('Title')
            ->columnSpan(3),
        Select::make('category')
            ->options([
                'person' => 'Person',
                'object' => 'Object',
                'location' => 'Location',
            ])
            ->columnSpan(3),
        Hidden::make('annotation_id'),
        Hidden::make('geometry'),
    ])
    ->addable(false)
    ->deletable(true)
    ->reorderable(false)
    ->columns(7)
    ->columnSpanFull()
    ->live()
    ->afterStateUpdated(function (?array $state, Set $set) {
        $canvasState = collect($state ?? [])->map(fn ($item) => [
            'id' => $item['annotation_id'],
            'target' => json_decode($item['geometry'] ?? '[]', true),
        ])->toArray();

        $set('annotations', $canvasState);
    }),

$model->syncAnnotations([
    [
        'annotation_id' => 'uuid-from-annotorious',
        'geometry' => ['selector' => ['type' => 'SvgSelector', 'value' => '<svg>...</svg>']],
        'metadata' => ['title' => 'My Label', 'score' => 0.95],  // optional
    ],
]);

// With colors — each annotation gets a unique color from the palette
ImageLabel::make('annotations')
    ->image(fn ($record) => $record?->getFirstMediaUrl())
    ->coloredAnnotations([
        '#ef4444', '#3b82f6', '#10b981', '#f59e0b',
        '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16',
        '#f97316', '#6366f1', '#14b8a6', '#e11d48',
    ])
    ->enableSquare()
    ->enablePolygon()
    ->live()

// Without colors — uses Annotorious default white/light styling
ImageLabel::make('annotations')
    ->image(fn ($record) => $record?->getFirstMediaUrl())
    ->enableSquare()
    ->enablePolygon()
    ->live()

use Zielu92\FilamentImageLabeler\Support\AnnotationColor;

Placeholder::make('color_swatch')
    ->hiddenLabel()
    ->content(function (Get $get) use ($palette): HtmlString {
        $id = $get('annotation_id') ?? '';
        $color = AnnotationColor::forId($id, $palette);

        return new HtmlString(
            '<div style="width: 24px; height: 24px; border-radius: 4px; '
            . 'background-color: ' . $color . ';"></div>'
        );
    })

ImageLabel::make('annotations')
    ->image(function ($record, Get $get) {
        if ($record && $record->getFirstMedia()) {
            return $record->getFirstTemporaryUrl(now()->addMinutes(30));
        }

        // Handle temporary upload during create...
        return null;
    })

public function test_sync_creates_annotations(): void
{
    $photo = Photo::factory()->create();

    $photo->syncAnnotations([
        [
            'annotation_id' => 'ann-1',
            'geometry' => ['selector' => ['type' => 'FragmentSelector', 'value' => 'xywh=pixel:10,20,100,50']],
            'metadata' => ['label' => 'Person'],
        ],
    ]);

    $this->assertCount(1, $photo->annotations);
    $this->assertEquals('Person', $photo->annotations->first()->metadata['label']);
}
bash
php artisan migrate