定制 blackpig-creatif/chambre-noir 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

blackpig-creatif/chambre-noir

Composer 安装命令:

composer require blackpig-creatif/chambre-noir

包简介

Automatic image conversion and optimization for Filament FileUpload fields

README 文档

README

Automatic image conversion, responsive images, attribution, and gallery management for FilamentPHP v5.

ChambreNoir extends Filament's FileUpload with automatic image conversions on upload, responsive image rendering via <picture> and srcset, and built-in photographer attribution. The core upload feature stores JSON in any column with no additional tables. The optional Gallery system adds first-class entities for managing curated image collections.

Requirements

  • PHP 8.2+
  • Laravel 11+ / 12+
  • FilamentPHP 4+ / 5+
  • Imagick PHP extension
  • Intervention Image 3.x

Installation

composer require blackpig-creatif/chambre-noir

Publish the config (optional):

php artisan vendor:publish --tag=chambre-noir-config

Quick Start

1. Create a Conversion Preset

php artisan chambre-noir:make-conversion Hero

This generates app/BlackpigCreatif/ChambreNoir/Conversions/HeroConversion.php:

<?php

namespace App\BlackpigCreatif\ChambreNoir\Conversions;

use BlackpigCreatif\ChambreNoir\Conversions\BaseConversion;

class HeroConversion extends BaseConversion
{
    protected int $defaultQuality = 85;

    protected function define(): array
    {
        return [
            'small'  => ['width' => 600,  'height' => 600,  'fit' => 'max'],
            'medium' => ['width' => 1200, 'height' => 1200, 'fit' => 'max'],
            'large'  => ['width' => 2400, 'height' => 2400, 'fit' => 'max'],
        ];
    }
}

2. Use in a Filament Form

use BlackpigCreatif\ChambreNoir\Forms\Components\RetouchMediaUpload;
use App\BlackpigCreatif\ChambreNoir\Conversions\HeroConversion;

RetouchMediaUpload::make('hero_image')
    ->preset(HeroConversion::class)
    ->disk('public')
    ->directory('pages/hero')
    ->required();

3. Add the Trait to Your Model

use BlackpigCreatif\ChambreNoir\Concerns\HasRetouchMedia;

class Page extends Model
{
    use HasRetouchMedia;

    protected $casts = [
        'hero_image' => 'array',
    ];
}

4. Render in Blade

{!! $page->getPicture('hero_image', ['alt' => $page->title, 'class' => 'w-full h-auto']) !!}

How It Works

When an image is uploaded through RetouchMediaUpload:

  1. Filament's FileUpload saves the original to disk
  2. ChambreNoir creates conversions based on the preset
  3. A JSON structure is stored in the database column
{
  "original": "pages/hero/image.jpg",
  "conversions": {
    "small": "pages/hero/conversions/image-small.jpg",
    "medium": "pages/hero/conversions/image-medium.jpg",
    "large": "pages/hero/conversions/image-large.jpg"
  },
  "attribution": {
    "name": "Jane Smith",
    "link": "https://janesmith.photography"
  },
  "preset": "App\\BlackpigCreatif\\ChambreNoir\\Conversions\\HeroConversion"
}

Old images are automatically cleaned up when replaced or removed.

RetouchMediaUpload

Drop-in replacement for Filament's FileUpload. Image-only by default.

Conversions via Preset (Recommended)

RetouchMediaUpload::make('image')
    ->preset(HeroConversion::class)

Inline Conversions

RetouchMediaUpload::make('image')
    ->conversions([
        'thumb'  => ['width' => 200, 'height' => 200, 'fit' => 'crop'],
        'medium' => ['width' => 800, 'height' => 600, 'fit' => 'contain'],
        'large'  => ['width' => 1920, 'height' => 1080, 'fit' => 'max'],
    ])

Config-based Presets (Legacy)

String keys reference config/chambre-noir.php presets. BaseConversion classes are preferred for new projects.

RetouchMediaUpload::make('image')
    ->preset('hero')

Disable Conversions

RetouchMediaUpload::make('document')
    ->withoutConversions()

All standard FileUpload methods (disk(), directory(), imageEditor(), maxSize(), etc.) work as expected.

Conversion Presets

Presets define the image sizes generated on upload. Extend BaseConversion and implement define():

use BlackpigCreatif\ChambreNoir\Conversions\BaseConversion;

class ProductConversion extends BaseConversion
{
    protected int $defaultQuality = 90;
    protected string $defaultFit = 'contain';

    protected function define(): array
    {
        return [
            'thumb'  => ['width' => 300, 'height' => 300, 'fit' => 'crop'],
            'medium' => ['width' => 800, 'height' => 800],
            'large'  => ['width' => 1600, 'height' => 1600, 'quality' => 92],
        ];
    }
}

Each conversion accepts width, height, fit, and quality. Missing values inherit from class defaults.

Fit Methods

Fit Behaviour Use case
crop Exact dimensions, crops overflow Thumbnails, avatars
contain Fits within bounds, preserves ratio General images
max Scales down only, never up Responsive images
fill Fills dimensions, may distort Background fills

Composition

Presets can include other presets via includes():

class FullConversion extends BaseConversion
{
    protected function includes(): array
    {
        return [SocialImageConversion::class];
    }

    protected function define(): array
    {
        return [
            'thumb'  => ['width' => 200, 'height' => 200, 'fit' => 'crop'],
            'large'  => ['width' => 1920, 'height' => 1080, 'fit' => 'max'],
        ];
    }
}

Included conversions are merged first; the defining class can override them.

Responsive Configuration

Override getResponsiveConfig() to control <picture> and srcset behaviour:

public function getResponsiveConfig(): array
{
    return [
        'default' => 'medium',
        'srcset' => [
            'small'  => true,
            'medium' => true,
            'large'  => true,
        ],
        'picture' => [
            'large'  => '(min-width: 1024px)',
            'medium' => '(min-width: 768px)',
            'small'  => null, // fallback
        ],
        'sizes' => '(min-width: 768px) 50vw, 100vw',
    ];
}

See Conversion Presets for the full reference.

Built-in Preset

SocialImageConversion ships with ChambreNoir, producing og (1200x630) and twitter (1200x600) crops at 90% quality.

HasRetouchMedia Trait

Add to any Eloquent model (or Atelier block) to access rendering helpers. Ensure the field is cast to array.

Rendering Methods

// <picture> with responsive sources (recommended)
$model->getPicture('image', ['alt' => '...', 'class' => '...'])

// <img> with srcset attribute
$model->getImageWithSrcset('image', $sizes, ['alt' => '...'])

// <figure> with optional <figcaption> (uses attribution if present)
$model->getFigure('image', [
    'mode'    => 'picture',           // 'picture', 'srcset', or 'image'
    'image'   => ['alt' => '...', 'class' => '...'],
    'figure'  => ['class' => 'relative'],
    'caption' => ['class' => 'text-sm text-gray-600'],
])

// Simple <img> for a specific conversion
$model->getImage('image', 'thumb', ['alt' => '...'])

URL Access

$model->getMediaUrl('image', 'large')           // URL string
$model->getMediaUrl('image', 'original')         // Original file URL
$model->getMediaUrls('gallery', 'medium')        // Array of URLs (multiple files)
$model->getMediaPath('image', 'large')           // Storage path (not URL)

Srcset Attributes (Manual)

<img {!! $model->getSrcset('image') !!} alt="..." class="...">

Returns srcset="..." sizes="..." src="..." attributes for manual <img> construction.

Graceful Degradation

All methods handle missing conversions, legacy string paths, and null data without throwing exceptions. getPicture() falls back to a simple <img>, getSrcset() returns an empty string, and getImageWithSrcset() falls back to the original URL.

See Responsive Images for detailed usage.

Attribution

ChambreNoir stores photographer credit and portfolio link alongside image data.

Enable in Form

RetouchMediaUpload::make('image')
    ->preset(HeroConversion::class)
    ->attribution()
    ->required();

This adds "Credit" and "Portfolio Link" fields below the upload component.

Requirement Control

// Inherits from component's required() state (default)
->attribution()

// Force all attribution fields required
->attributionRequired(true)

// Force all optional
->attributionRequired(false)

// Granular control
->attributionRequired(['name' => true, 'link' => false])

// Dynamic
->attributionRequired(fn ($get) => $get('requires_credit'))

Display in Templates

{{-- Automatic: getFigure() includes attribution as figcaption --}}
{!! $post->getFigure('image', [
    'image'   => ['alt' => $post->title],
    'caption' => ['class' => 'text-sm text-gray-600'],
]) !!}

{{-- Manual --}}
@if($post->hasAttribution('image'))
    <p>
        Photo by
        @if($link = $post->getAttributionLink('image'))
            <a href="{{ $link }}" target="_blank" rel="noopener noreferrer">
                {{ $post->getAttributionName('image') }}
            </a>
        @else
            {{ $post->getAttributionName('image') }}
        @endif
    </p>
@endif

Attribution Methods

$model->getAttribution('image')      // ['name' => '...', 'link' => '...'] or null
$model->getAttributionName('image')  // string or null
$model->getAttributionLink('image')  // string or null
$model->hasAttribution('image')      // bool

Gallery System

ChambreNoir ships a full gallery management system built on top of three additional tables. Galleries are managed through a dedicated Filament resource, attached to any Eloquent model via a polymorphic pivot, and rendered on the frontend via either raw Blade helpers or the included Atelier block.

Migrations

php artisan vendor:publish --tag=chambre-noir-migrations
php artisan migrate

This creates galleries, gallery_images, and galleryables tables.

Add the Relationship to Your Model

GalleryPickerField writes to a polymorphic galleryables pivot. Add the relationship to any model that will hold gallery attachments:

use BlackpigCreatif\ChambreNoir\Models\Gallery;

class Page extends Model
{
    public function galleries(): \Illuminate\Database\Eloquent\Relations\MorphToMany
    {
        return $this->morphToMany(Gallery::class, 'galleryable')
            ->withPivot('sort_order')
            ->orderBy('galleryables.sort_order');
    }
}

Attach Galleries in a Form

use BlackpigCreatif\ChambreNoir\Forms\Components\GalleryPickerField;

GalleryPickerField::make('galleries')
    ->multiple()

By default only active (published) galleries are shown. Options:

// Include unpublished galleries
GalleryPickerField::make('galleries')
    ->multiple()
    ->unpublished()

// Restrict to a specific conversion preset
GalleryPickerField::make('galleries')
    ->multiple()
    ->conversion('product')

Render in Blade

@foreach($page->galleries as $gallery)
    @foreach($gallery->images as $image)
        {!! $image->getPicture('image', ['alt' => $image->caption ?? '']) !!}
    @endforeach
@endforeach

Atelier Block

If Atelier is installed, a GalleriesBlock is registered automatically. It supports grid and carousel display, combined or tabbed multi-gallery modes, and an optional lightbox — all driven by Alpine.js with no additional dependencies.

See Gallery System for the full reference.

Artisan Commands

Make Conversion

php artisan chambre-noir:make-conversion ProductImage

Generates app/BlackpigCreatif/ChambreNoir/Conversions/ProductImageConversion.php extending BaseConversion with a scaffolded define() method and responsive config.

Regenerate Conversions

Regenerate image conversions across models, Atelier blocks, and Sceau SEO data:

# All images everywhere
php artisan chambre-noir:regenerate --all

# Only model images
php artisan chambre-noir:regenerate --models

# Only Atelier block images
php artisan chambre-noir:regenerate --blocks

# Only Sceau SEO images
php artisan chambre-noir:regenerate --seo

# Target a specific model
php artisan chambre-noir:regenerate --model="App\Models\Post"

# Target a specific block type
php artisan chambre-noir:regenerate --block-type="HeroBlock"

# Filter by field name or record ID
php artisan chambre-noir:regenerate --models --field=hero_image --id=42

# Filter by conversion class or disk
php artisan chambre-noir:regenerate --models --conversion="App\Conversions\HeroConversion"
php artisan chambre-noir:regenerate --all --disk=s3

# Preview without changes
php artisan chambre-noir:regenerate --all --dry-run

# Skip confirmation
php artisan chambre-noir:regenerate --all --force

# Backup old conversions before regenerating
php artisan chambre-noir:regenerate --all --backup

# JSON output for scripting
php artisan chambre-noir:regenerate --all --json

Configuration

Published to config/chambre-noir.php.

Key Default Description
quality 90 Global default quality (1-100). Override per-preset or per-conversion.
disk 'public' Default storage disk.
conversions_directory 'conversions' Subdirectory for conversion files within the upload directory.
presets [...] Legacy array-based presets. Use BaseConversion classes instead.
responsive.default_conversion 'medium' Fallback conversion for <img src=""> in responsive elements.
responsive.default_sizes null Default sizes attribute. null = auto-generate from widths.
responsive.auto_generate_sizes true Auto-calculate sizes from conversion widths and breakpoints.
responsive.breakpoints Tailwind defaults Breakpoints for auto-generated media queries.

Environment variables:

CHAMBRE_NOIR_QUALITY=85
CHAMBRE_NOIR_DISK=public

File Storage Structure

storage/app/public/pages/hero/
    image.jpg                              # Original
    conversions/
        image-small.jpg                    # 600x600
        image-medium.jpg                   # 1200x1200
        image-large.jpg                    # 2400x2400

Integration with Atelier

ChambreNoir is designed as a companion to Atelier. Use RetouchMediaUpload in block schemas and HasRetouchMedia on blocks:

use BlackpigCreatif\ChambreNoir\Concerns\HasRetouchMedia;

class HeroBlock extends BaseBlock
{
    use HasRetouchMedia;
    // ...
}

In block templates:

{!! $block->getPicture('background_image', [
    'alt'   => $block->getTranslated('title'),
    'class' => 'w-full h-full object-cover',
]) !!}

Image cleanup for block attributes is handled automatically by Atelier's BlockManager via ImageCleanupService.

Integration with Sceau

ChambreNoir's regeneration command supports Sceau SEO images. Use --seo to regenerate Open Graph and social sharing images managed by Sceau.

License

MIT. See LICENSE.md.

Credits

blackpig-creatif/chambre-noir 适用场景与选型建议

blackpig-creatif/chambre-noir 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 104 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 02 月 09 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「laravel」 「image-processing」 「filament」 「responsive-images」 「image-conversion」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 blackpig-creatif/chambre-noir 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 blackpig-creatif/chambre-noir 我们能提供哪些服务?
定制开发 / 二次开发

基于 blackpig-creatif/chambre-noir 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 104
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 38
  • 依赖项目数: 2
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-02-09