fixed migrations, changed settings to global settings, changed image list to have a "delete all" button instead of "create", fixed printing, added imagick for printing.

This commit is contained in:
2025-08-26 10:39:18 +02:00
parent 44dd0f2867
commit 9b1f6a479f
69 changed files with 17232 additions and 1263 deletions

View File

@@ -124,6 +124,21 @@ class ComfyUi implements ApiPluginInterface
$modelParams = $style->aiModel->parameters ?? []; $modelParams = $style->aiModel->parameters ?? [];
$styleParams = $style->parameters ?? []; $styleParams = $style->parameters ?? [];
// Ensure both parameters are arrays, decode JSON if needed
if (is_string($modelParams)) {
$modelParams = json_decode($modelParams, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$modelParams = [];
}
}
if (is_string($styleParams)) {
$styleParams = json_decode($styleParams, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$styleParams = [];
}
}
if (empty($modelParams) && empty($styleParams)) { if (empty($modelParams) && empty($styleParams)) {
throw new \Exception('ComfyUI workflow (parameters) is missing.'); throw new \Exception('ComfyUI workflow (parameters) is missing.');
} }

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class GetPrinterSetting extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'setting:get-printer';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get the raw value of the \'selected_printer\' setting.';
/**
* Execute the console command.
*/
public function handle()
{
$setting = \App\Models\Setting::where('key', 'selected_printer')->first();
if ($setting) {
$this->info('Raw value of \'selected_printer\' setting:');
$this->info(json_encode($setting->value, JSON_PRETTY_PRINT)); // Assuming \'value\' column holds the data
} else {
$this->info('\'selected_printer\' setting not found.');
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Filament\Pages;
use App\Models\Setting;
use App\Services\PrinterService;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Arr;
class GlobalSettings extends Page implements HasForms
{
use InteractsWithForms;
protected static ?string $navigationIcon = 'heroicon-o-cog';
protected static string $view = 'filament.pages.global-settings';
protected static ?string $navigationGroup = 'Admin';
public ?array $data = [];
public function mount(): void
{
$settings = Setting::all()->pluck('value', 'key')->toArray();
$this->form->fill($settings);
}
public function form(Form $form): Form
{
$printerService = new PrinterService();
$printers = $printerService->getPrinters();
$printerOptions = array_merge($printers, ['__custom__' => __('filament.resource.setting.form.custom_printer')]);
return $form
->schema([
TextInput::make('gallery_heading')
->label(__('filament.resource.setting.form.gallery_heading'))
->required(),
TextInput::make('new_image_timespan_minutes')
->label(__('filament.resource.setting.form.new_image_timespan_minutes'))
->numeric()
->required(),
TextInput::make('image_refresh_interval')
->label(__('filament.resource.setting.form.image_refresh_interval'))
->numeric()
->required(),
TextInput::make('max_number_of_copies')
->label(__('filament.resource.setting.form.max_number_of_copies'))
->numeric()
->required(),
Toggle::make('show_print_button')
->label(__('filament.resource.setting.form.show_print_button')),
Select::make('selected_printer')
->label(__('filament.resource.setting.form.printer'))
->options($printerOptions)
->reactive(),
TextInput::make('custom_printer_address')
->label(__('filament.resource.setting.form.custom_printer_address'))
->visible(fn ($get) => $get('selected_printer') === '__custom__'),
])
->statePath('data');
}
public function save(): void
{
$data = $this->form->getState();
// if a non-custom printer is selected, clear the custom address
if (Arr::get($data, 'selected_printer') !== '__custom__') {
$data['custom_printer_address'] = '';
}
foreach ($data as $key => $value) {
Setting::where('key', $key)->update(['value' => $value]);
}
Notification::make()
->title(__('settings.saved_successfully'))
->success()
->send();
}
protected function getFormActions(): array
{
return [
\Filament\Actions\Action::make('save')
->label(__('settings.save_button'))
->submit('save'),
];
}
}

View File

@@ -3,21 +3,13 @@
namespace App\Filament\Pages; namespace App\Filament\Pages;
use Filament\Pages\Page; use Filament\Pages\Page;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Concerns\InteractsWithForms;
use App\Models\Plugin; use App\Models\Plugin;
class Plugins extends Page implements Tables\Contracts\HasTable, HasForms class Plugins extends Page
{ {
use Tables\Concerns\InteractsWithTable;
use InteractsWithForms;
protected static ?string $navigationIcon = 'heroicon-o-puzzle-piece'; protected static ?string $navigationIcon = 'heroicon-o-puzzle-piece';
protected static ?string $navigationGroup = 'Plugins'; protected static ?string $navigationGroup = 'Plugins';
protected static ?string $navigationLabel = 'Plugin List'; protected static ?string $navigationLabel = 'Plugin List';
@@ -27,31 +19,7 @@ class Plugins extends Page implements Tables\Contracts\HasTable, HasForms
protected static ?string $slug = 'list-plugins'; protected static ?string $slug = 'list-plugins';
public function table(Table $table): Table public $plugins;
{
return $table
->query(Plugin::query()) // Use a dummy query
->columns([
Tables\Columns\TextColumn::make('name')
->label('Name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('identifier')
->label('Identifier'),
Tables\Columns\IconColumn::make('enabled')
->label('Enabled')
->boolean(),
Tables\Columns\IconColumn::make('configured')
->label('Configured')
->boolean()
->tooltip(fn ($record) => $record->configured ? 'Has ApiProvider record' : 'No ApiProvider record'),
Tables\Columns\TextColumn::make('file_path')
->label('File Path')
->toggleable(isToggledHiddenByDefault: true),
]);
}
public $plugins;
public function mount(): void public function mount(): void
{ {
@@ -67,9 +35,4 @@ class Plugins extends Page implements Tables\Contracts\HasTable, HasForms
{ {
return __('filament.navigation.plugin_list'); return __('filament.navigation.plugin_list');
} }
public function currentlyValidatingForm(\Filament\Forms\ComponentContainer|null $form): void
{
// No form validation needed for this page
}
} }

View File

@@ -32,11 +32,11 @@ class AiModelResource extends Resource
->schema([ ->schema([
Forms\Components\Section::make() Forms\Components\Section::make()
->schema([ ->schema([
Select::make('api_provider_id') Select::make('apiProviders')
->label(__('filament.resource.ai_model.form.api_provider')) ->label(__('filament.resource.ai_model.form.api_providers'))
->relationship('apiProviders', 'name') ->relationship('apiProviders', 'name')
->multiple()
->live() ->live()
->nullable()
->afterStateUpdated(function (callable $set) { ->afterStateUpdated(function (callable $set) {
$set('model_search_result', null); $set('model_search_result', null);
}), }),
@@ -44,20 +44,24 @@ class AiModelResource extends Resource
->label(__('filament.resource.ai_model.form.search_model')) ->label(__('filament.resource.ai_model.form.search_model'))
->searchable() ->searchable()
->live() ->live()
->hidden(fn (callable $get) => !static::canSearchModels($get('api_provider_id'))) ->hidden(fn (callable $get) => !static::canSearchModelsWithAnyProvider($get('apiProviders')))
->getSearchResultsUsing(function (string $search, callable $get) { ->getSearchResultsUsing(function (string $search, callable $get) {
$apiProviderId = $get('api_provider_id'); $apiProviderIds = $get('apiProviders');
if (!$apiProviderId) { if (empty($apiProviderIds)) {
return []; return [];
} }
$pluginInstance = static::getPluginInstance($apiProviderId);
if ($pluginInstance && method_exists($pluginInstance, 'searchModels')) { // Try each API provider until we find one that works
$models = $pluginInstance->searchModels($search); foreach ($apiProviderIds as $apiProviderId) {
$options = []; $pluginInstance = static::getPluginInstance($apiProviderId);
foreach ($models as $model) { if ($pluginInstance && method_exists($pluginInstance, 'searchModels')) {
$options[json_encode(['name' => $model['name'], 'id' => $model['id'], 'type' => $model['type'] ?? null])] = $model['name'] . ' (' . $model['id'] . ')'; $models = $pluginInstance->searchModels($search);
$options = [];
foreach ($models as $model) {
$options[json_encode(['name' => $model['name'], 'id' => $model['id'], 'type' => $model['type'] ?? null, 'api_provider_id' => $apiProviderId])] = $model['name'] . ' (' . $model['id'] . ')';
}
return $options;
} }
return $options;
} }
return []; return [];
}) })
@@ -103,22 +107,28 @@ class AiModelResource extends Resource
]); ]);
} }
protected static function canSearchModels(?int $apiProviderId): bool protected static function canSearchModelsWithAnyProvider(?array $apiProviderIds): bool
{ {
if (!$apiProviderId) { if (empty($apiProviderIds)) {
return false; return false;
} }
$apiProvider = ApiProvider::find($apiProviderId);
if (!$apiProvider || !$apiProvider->plugin) { foreach ($apiProviderIds as $apiProviderId) {
return false; $apiProvider = ApiProvider::find($apiProviderId);
} if (!$apiProvider || !$apiProvider->plugin) {
try { continue;
$pluginInstance = PluginLoader::getPlugin($apiProvider->plugin, $apiProvider); }
return method_exists($pluginInstance, 'searchModels'); try {
} catch (\Exception $e) { $pluginInstance = PluginLoader::getPlugin($apiProvider->plugin, $apiProvider);
// Log the exception if needed if (method_exists($pluginInstance, 'searchModels')) {
return false; return true;
}
} catch (\Exception $e) {
// Log the exception if needed
continue;
}
} }
return false;
} }
protected static function getPluginInstance(?int $apiProviderId): ?ApiPluginInterface protected static function getPluginInstance(?int $apiProviderId): ?ApiPluginInterface
@@ -138,6 +148,24 @@ class AiModelResource extends Resource
} }
} }
protected static function canSearchModels(?int $apiProviderId): bool
{
if (!$apiProviderId) {
return false;
}
$apiProvider = ApiProvider::find($apiProviderId);
if (!$apiProvider || !$apiProvider->plugin) {
return false;
}
try {
$pluginInstance = PluginLoader::getPlugin($apiProvider->plugin, $apiProvider);
return method_exists($pluginInstance, 'searchModels');
} catch (\Exception $e) {
// Log the exception if needed
return false;
}
}
public static function table(Table $table): Table public static function table(Table $table): Table
{ {
return $table return $table
@@ -148,7 +176,7 @@ class AiModelResource extends Resource
Tables\Columns\IconColumn::make('enabled') Tables\Columns\IconColumn::make('enabled')
->label(__('filament.resource.ai_model.table.enabled')) ->label(__('filament.resource.ai_model.table.enabled'))
->boolean(), ->boolean(),
TextColumn::make('apiProviders.name')->label(__('filament.resource.ai_model.table.api_providers'))->searchable()->sortable(), TextColumn::make('apiProviders.name')->label(__('filament.resource.ai_model.table.api_providers'))->searchable()->sortable()->limit(50),
]) ])
->filters([ ->filters([
// //

View File

@@ -54,9 +54,6 @@ class ImageResource extends Resource
Tables\Actions\BulkActionGroup::make([ Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(), Tables\Actions\DeleteBulkAction::make(),
]), ]),
])
->emptyStateActions([
Tables\Actions\CreateAction::make(),
]); ]);
} }
@@ -71,8 +68,7 @@ class ImageResource extends Resource
{ {
return [ return [
'index' => Pages\ListImages::route('/'), 'index' => Pages\ListImages::route('/'),
'create' => Pages\CreateImage::route('/create'),
'edit' => Pages\EditImage::route('/{record}/edit'), 'edit' => Pages\EditImage::route('/{record}/edit'),
]; ];
} }
} }

View File

@@ -1,17 +0,0 @@
<?php
namespace App\Filament\Resources\ImageResource\Pages;
use App\Filament\Resources\ImageResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateImage extends CreateRecord
{
protected static string $resource = ImageResource::class;
protected function getRedirectUrl(): string
{
return $this->getResource()::getUrl('index');
}
}

View File

@@ -7,6 +7,8 @@ use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Illuminate\Contracts\Pagination\Paginator; use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Storage;
use Filament\Notifications\Notification;
class ListImages extends ListRecords class ListImages extends ListRecords
{ {
@@ -15,7 +17,25 @@ class ListImages extends ListRecords
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make(), Actions\Action::make('delete_all')
->label('Delete All Images')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->action(function () {
// Delete all images from storage
Storage::disk('public')->deleteDirectory('images');
Storage::disk('public')->makeDirectory('images');
// Show success notification
Notification::make()
->title('All images deleted successfully')
->success()
->send();
// Refresh the page
$this->redirect(static::getUrl());
}),
]; ];
} }

View File

@@ -1,105 +0,0 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\SettingResource\Pages;
use App\Filament\Resources\SettingResource\RelationManagers;
use App\Models\Setting;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Fieldset;
class SettingResource extends Resource
{
protected static ?string $model = Setting::class;
protected static ?string $navigationIcon = 'heroicon-o-cog';
protected static ?string $navigationGroup = 'Einstellungen';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('key')
->label(__('Key'))
->required()
->maxLength(255)
->hiddenOn('edit'),
Forms\Components\Fieldset::make()
->label(fn (?Setting $record) => $record ? __('filament.resource.setting.form.' . $record->key) : __('New Setting'))
->schema([
TextInput::make('value')
->label(__('Bitte Wert eingeben'))
->disableLabel(fn (?Setting $record) => $record && ($record->key !== 'image_refresh_interval' && $record->key !== 'new_image_timespan_minutes' && $record->key !== 'gallery_heading'))
->formatStateUsing(function (?string $state, ?Setting $record) {
if ($record && $record->key === 'image_refresh_interval') {
return (int)$state / 1000;
} else if ($record && $record->key === 'new_image_timespan_minutes') {
return (int)$state;
}
return $state;
})
->dehydrateStateUsing(function (?string $state, ?Setting $record) {
if ($record && $record->key === 'image_refresh_interval') {
return (int)$state * 1000;
} else if ($record && $record->key === 'new_image_timespan_minutes') {
return (int)$state;
}
return $state;
})
])
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('key')->label(__('Key'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('value')->label(__('Value'))->searchable()->sortable(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
])
->emptyStateActions([
Tables\Actions\CreateAction::make(),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSettings::route('/'),
'create' => Pages\CreateSetting::route('/create'),
'edit' => Pages\EditSetting::route('/{record}/edit'),
];
}
public static function getNavigationGroup(): ?string
{
return __('filament.navigation.groups.settings');
}
}

View File

@@ -1,12 +0,0 @@
<?php
namespace App\Filament\Resources\SettingResource\Pages;
use App\Filament\Resources\SettingResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateSetting extends CreateRecord
{
protected static string $resource = SettingResource::class;
}

View File

@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\SettingResource\Pages;
use App\Filament\Resources\SettingResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditSetting extends EditRecord
{
protected static string $resource = SettingResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\SettingResource\Pages;
use App\Filament\Resources\SettingResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListSettings extends ListRecords
{
protected static string $resource = SettingResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -109,11 +109,22 @@ class ImageController extends Controller
public function styleChangeRequest(Request $request) public function styleChangeRequest(Request $request)
{ {
// Log the incoming request for debugging
\Illuminate\Support\Facades\Log::info('styleChangeRequest called', [
'image_id' => $request->image_id,
'style_id' => $request->style_id,
'all_params' => $request->all()
]);
// Same-origin check // Same-origin check
$appUrl = config('app.url'); $appUrl = config('app.url');
$referer = $request->headers->get('referer'); $referer = $request->headers->get('referer');
if ($referer && parse_url($referer, PHP_URL_HOST) !== parse_url($appUrl, PHP_URL_HOST)) { if ($referer && parse_url($referer, PHP_URL_HOST) !== parse_url($appUrl, PHP_URL_HOST)) {
\Illuminate\Support\Facades\Log::warning('Unauthorized styleChangeRequest', [
'referer' => $referer,
'app_url' => $appUrl
]);
return response()->json(['error' => 'Unauthorized: Request must originate from the same domain.'], 403); return response()->json(['error' => 'Unauthorized: Request must originate from the same domain.'], 403);
} }
@@ -127,7 +138,7 @@ class ImageController extends Controller
if ($request->style_id) { if ($request->style_id) {
$style = Style::with(['aiModel' => function ($query) { $style = Style::with(['aiModel' => function ($query) {
$query->where('enabled', true)->with(['apiProviders' => function ($query) { $query->where('enabled', true)->with(['primaryApiProvider', 'apiProviders' => function ($query) {
$query->where('enabled', true); $query->where('enabled', true);
}]); }]);
}])->find($request->style_id); }])->find($request->style_id);
@@ -136,7 +147,7 @@ class ImageController extends Controller
$defaultStyleSetting = \App\Models\Setting::where('key', 'default_style_id')->first(); $defaultStyleSetting = \App\Models\Setting::where('key', 'default_style_id')->first();
if ($defaultStyleSetting && $defaultStyleSetting->value) { if ($defaultStyleSetting && $defaultStyleSetting->value) {
$style = Style::with(['aiModel' => function ($query) { $style = Style::with(['aiModel' => function ($query) {
$query->where('enabled', true)->with(['apiProviders' => function ($query) { $query->where('enabled', true)->with(['primaryApiProvider', 'apiProviders' => function ($query) {
$query->where('enabled', true); $query->where('enabled', true);
}]); }]);
}])->find($defaultStyleSetting->value); }])->find($defaultStyleSetting->value);
@@ -144,14 +155,38 @@ class ImageController extends Controller
} }
if (!$style || !$style->aiModel || $style->aiModel->apiProviders->isEmpty()) { if (!$style || !$style->aiModel || $style->aiModel->apiProviders->isEmpty()) {
\Illuminate\Support\Facades\Log::warning('Style or provider not found', [
'style' => $style ? $style->toArray() : null,
'ai_model' => $style && $style->aiModel ? $style->aiModel->toArray() : null
]);
return response()->json(['error' => __('api.style_or_provider_not_found')], 404); return response()->json(['error' => __('api.style_or_provider_not_found')], 404);
} }
try { try {
$apiProvider = $style->aiModel->apiProviders->first(); // Get the first enabled API provider // Use the primary API provider for this AI model if available
$apiProvider = $style->aiModel->primaryApiProvider;
if (!$apiProvider) { if (!$apiProvider) {
// Fallback to the first enabled API provider from the many-to-many relationship
$apiProvider = $style->aiModel->apiProviders->where('enabled', true)->first();
}
if (!$apiProvider) {
// If no enabled provider found, try any provider
$apiProvider = $style->aiModel->apiProviders->first();
}
if (!$apiProvider) {
\Illuminate\Support\Facades\Log::error('No API provider found for style', [
'style_id' => $style->id,
'ai_model_id' => $style->aiModel->id
]);
return response()->json(['error' => __('api.style_or_provider_not_found')], 404); return response()->json(['error' => __('api.style_or_provider_not_found')], 404);
} }
\Illuminate\Support\Facades\Log::info('Selected API provider for style change', [
'api_provider_id' => $apiProvider->id,
'api_provider_name' => $apiProvider->name,
'plugin' => $apiProvider->plugin
]);
$plugin = PluginLoader::getPlugin($apiProvider->plugin, $apiProvider); $plugin = PluginLoader::getPlugin($apiProvider->plugin, $apiProvider);
$result = $plugin->processImageStyleChange($image, $style); $result = $plugin->processImageStyleChange($image, $style);
@@ -162,12 +197,22 @@ class ImageController extends Controller
$image->save(); $image->save();
// Return the prompt_id for WebSocket tracking // Return the prompt_id for WebSocket tracking
\Illuminate\Support\Facades\Log::info('Style change request completed', [
'prompt_id' => $result['prompt_id'],
'image_uuid' => $image->uuid
]);
return response()->json([ return response()->json([
'message' => 'Style change request sent.', 'message' => 'Style change request sent.',
'prompt_id' => $result['prompt_id'], 'prompt_id' => $result['prompt_id'],
'image_uuid' => $image->uuid, // Pass image UUID for frontend tracking 'image_uuid' => $image->uuid, // Pass image UUID for frontend tracking
'plugin' => $apiProvider->plugin, // Pass plugin name for frontend handling
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('Error in styleChangeRequest', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return response()->json(['error' => $e->getMessage()], 500); return response()->json(['error' => $e->getMessage()], 500);
} }
} }
@@ -230,8 +275,10 @@ class ImageController extends Controller
Log::info('fetchStyledImage called.', ['prompt_id' => $promptId]); Log::info('fetchStyledImage called.', ['prompt_id' => $promptId]);
try { try {
// Find the image associated with the prompt_id, eagerly loading relationships // Find the image associated with the prompt_id, eagerly loading relationships
$image = Image::with(['style.aiModel.apiProviders' => function ($query) { $image = Image::with(['style.aiModel' => function ($query) {
$query->where('enabled', true); $query->with(['primaryApiProvider', 'apiProviders' => function ($query) {
$query->where('enabled', true);
}]);
}])->where('comfyui_prompt_id', $promptId)->first(); }])->where('comfyui_prompt_id', $promptId)->first();
if (!$image) { if (!$image) {
@@ -259,7 +306,16 @@ class ImageController extends Controller
Log::warning('fetchStyledImage: No enabled API Providers found for AI Model.', ['ai_model_id' => $style->aiModel->id]); Log::warning('fetchStyledImage: No enabled API Providers found for AI Model.', ['ai_model_id' => $style->aiModel->id]);
return response()->json(['error' => __('api.style_or_provider_not_found')], 404); return response()->json(['error' => __('api.style_or_provider_not_found')], 404);
} }
$apiProvider = $style->aiModel->apiProviders->first(); // Use the primary API provider for this AI model if available
$apiProvider = $style->aiModel->primaryApiProvider;
if (!$apiProvider) {
// Fallback to the first enabled API provider from the many-to-many relationship
$apiProvider = $style->aiModel->apiProviders->where('enabled', true)->first();
}
if (!$apiProvider) {
// If no enabled provider found, try any provider
$apiProvider = $style->aiModel->apiProviders->first();
}
Log::info('fetchStyledImage: API Provider found.', ['api_provider_id' => $apiProvider->id, 'api_provider_name' => $apiProvider->name]); Log::info('fetchStyledImage: API Provider found.', ['api_provider_id' => $apiProvider->id, 'api_provider_name' => $apiProvider->name]);
Log::info('Fetching base64 image from plugin.', ['prompt_id' => $promptId, 'api_provider' => $apiProvider->name]); Log::info('Fetching base64 image from plugin.', ['prompt_id' => $promptId, 'api_provider' => $apiProvider->name]);
@@ -310,9 +366,85 @@ class ImageController extends Controller
} }
} }
public function getComfyUiUrl() public function getComfyUiUrl(Request $request)
{ {
$apiProvider = ApiProvider::where('plugin', 'comfyui')->where('enabled', true)->first(); $styleId = $request->query('style_id');
$imageUuid = $request->query('image_uuid');
$apiProvider = null;
// If style_id is provided, get the API provider for that style
if ($styleId) {
$style = Style::with(['aiModel' => function ($query) {
$query->where('enabled', true)->with(['primaryApiProvider', 'apiProviders' => function ($query) {
$query->where('enabled', true);
}]);
}])->find($styleId);
if ($style && $style->aiModel) {
// Use the primary API provider for this AI model if available
$apiProvider = $style->aiModel->primaryApiProvider;
if (!$apiProvider) {
// Fallback to the first enabled API provider from the many-to-many relationship
$apiProvider = $style->aiModel->apiProviders->where('enabled', true)->first();
}
if (!$apiProvider) {
// If no enabled provider found, try any provider
$apiProvider = $style->aiModel->apiProviders->first();
}
}
}
// If image_uuid is provided, get the API provider for that image's style
elseif ($imageUuid) {
$image = Image::with(['style.aiModel' => function ($query) {
$query->with(['primaryApiProvider', 'apiProviders' => function ($query) {
$query->where('enabled', true);
}]);
}])->where('uuid', $imageUuid)->first();
if ($image && $image->style && $image->style->aiModel) {
// Use the primary API provider for this AI model if available
$apiProvider = $image->style->aiModel->primaryApiProvider;
if (!$apiProvider) {
// Fallback to the first enabled API provider from the many-to-many relationship
$apiProvider = $image->style->aiModel->apiProviders->where('enabled', true)->first();
}
if (!$apiProvider) {
// If no enabled provider found, try any provider
$apiProvider = $image->style->aiModel->apiProviders->first();
}
}
}
// Fallback to the old behavior if no style_id or image_uuid is provided
else {
// Try to get a default style if none is provided
$defaultStyleSetting = \App\Models\Setting::where('key', 'default_style_id')->first();
if ($defaultStyleSetting && $defaultStyleSetting->value) {
$style = Style::with(['aiModel' => function ($query) {
$query->where('enabled', true)->with(['primaryApiProvider', 'apiProviders' => function ($query) {
$query->where('enabled', true);
}]);
}])->find($defaultStyleSetting->value);
if ($style && $style->aiModel) {
// Use the primary API provider for this AI model if available
$apiProvider = $style->aiModel->primaryApiProvider;
if (!$apiProvider) {
// Fallback to the first enabled API provider from the many-to-many relationship
$apiProvider = $style->aiModel->apiProviders->where('enabled', true)->first();
}
if (!$apiProvider) {
// If no enabled provider found, try any provider
$apiProvider = $style->aiModel->apiProviders->first();
}
}
}
// If still no API provider, use the first available ComfyUI provider
if (!$apiProvider) {
$apiProvider = ApiProvider::where('plugin', 'ComfyUi')->where('enabled', true)->first();
}
}
if (!$apiProvider) { if (!$apiProvider) {
return response()->json(['error' => 'No enabled ComfyUI API provider found.'], 404); return response()->json(['error' => 'No enabled ComfyUI API provider found.'], 404);

View File

@@ -4,12 +4,11 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Exception\ProcessFailedException; use App\Services\PrinterService;
use Symfony\Component\Process\Process;
class PrintController extends Controller class PrintController extends Controller
{ {
public function printImage(Request $request) public function printImage(Request $request, PrinterService $printerService)
{ {
$request->validate([ $request->validate([
'image_path' => 'required|string', 'image_path' => 'required|string',
@@ -18,36 +17,32 @@ class PrintController extends Controller
$imagePath = public_path(str_replace(url('/'), '', $request->input('image_path'))); $imagePath = public_path(str_replace(url('/'), '', $request->input('image_path')));
$quantity = $request->input('quantity'); $quantity = $request->input('quantity');
// Retrieve printer name from global settings using standard Eloquent
$printerName = \App\Models\Setting::where('key', 'selected_printer')->value('value');
if (!$printerName) {
Log::error("PrintController: Default printer name not found in settings.");
return response()->json(['error' => 'Default printer not configured.'], 500);
}
if (!$printerName) {
Log::error("PrintController: Default printer name not found in settings.");
return response()->json(['error' => 'Default printer not configured.'], 500);
}
if (!file_exists($imagePath)) { if (!file_exists($imagePath)) {
Log::error("PrintController: Image file not found at {$imagePath}"); Log::error("PrintController: Image file not found at {$imagePath}");
return response()->json(['error' => 'Image file not found.'], 404); return response()->json(['error' => 'Image file not found.'], 404);
} }
// IMPORTANT: Replace this command with one that works in your environment. $printSuccess = $printerService->printImage($imagePath, $printerName, $quantity);
// Examples:
// Linux/macOS: $command = ['lpr', '-#', $quantity, $imagePath];
// Windows (assuming a shared printer named 'MyNetworkPrinter'):
// $command = ['print', '/d:\\MyNetworkPrinter', $imagePath];
// You might need to install additional software or configure your system
// to enable command-line printing.
// For a more robust solution, consider a dedicated print server application
// or a commercial print API.
$command = ['echo', "Simulating print of {$quantity} copies of {$imagePath}"]; // Placeholder
try { if ($printSuccess) {
$process = new Process($command); Log::info("PrintController: Successfully sent print command for {$imagePath} (x{$quantity}) to {$printerName}");
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
Log::info("PrintController: Successfully sent print command for {$imagePath} (x{$quantity})");
return response()->json(['message' => 'Print command sent successfully.']); return response()->json(['message' => 'Print command sent successfully.']);
} catch (ProcessFailedException $exception) { } else {
Log::error("PrintController: Print command failed. Error: " . $exception->getMessage()); Log::error("PrintController: Failed to send print command for {$imagePath} (x{$quantity}) to {$printerName}");
return response()->json(['error' => 'Failed to send print command.', 'details' => $exception->getMessage()], 500); return response()->json(['error' => 'Failed to send print command.'], 500);
} }
} }
} }

View File

@@ -4,6 +4,7 @@ namespace App\Http\Middleware;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Inertia\Middleware; use Inertia\Middleware;
use App\Models\Setting;
class HandleInertiaRequests extends Middleware class HandleInertiaRequests extends Middleware
{ {
@@ -35,6 +36,7 @@ class HandleInertiaRequests extends Middleware
'user' => $request->user(), 'user' => $request->user(),
], ],
'locale' => app()->getLocale(), 'locale' => app()->getLocale(),
'settings' => Setting::all()->pluck('value', 'key'),
'translations' => function () use ($request) { 'translations' => function () use ($request) {
$currentLocale = app()->getLocale(); // Store current locale $currentLocale = app()->getLocale(); // Store current locale
$requestedLocale = $request->input('locale', $currentLocale); $requestedLocale = $request->input('locale', $currentLocale);
@@ -47,8 +49,6 @@ class HandleInertiaRequests extends Middleware
// Add other translation files as needed // Add other translation files as needed
]; ];
dd($lang); // <-- ADDED FOR DEBUGGING
app()->setLocale($currentLocale); // Revert to original locale app()->setLocale($currentLocale); // Revert to original locale
return $lang; return $lang;
}, },

View File

@@ -20,6 +20,11 @@ class AiModel extends Model
'parameters' => 'array', 'parameters' => 'array',
]; ];
public function primaryApiProvider()
{
return $this->belongsTo(ApiProvider::class, 'api_provider_id');
}
public function apiProviders() public function apiProviders()
{ {
return $this->belongsToMany(ApiProvider::class, 'ai_model_api_provider'); return $this->belongsToMany(ApiProvider::class, 'ai_model_api_provider');

View File

@@ -0,0 +1,119 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Log;
class PrinterService
{
public function getPrinters(): array
{
$printers = [];
$os = strtoupper(substr(PHP_OS, 0, 3));
if ($os === 'WIN') {
// Windows implementation
$output = shell_exec('wmic printer get name');
if ($output != "") {
$lines = explode(PHP_EOL, trim($output));
$start = 1; // Skip header on Windows
for ($i = $start; $i < count($lines); $i++) {
// Remove all control characters and non-printable characters, then trim
$printerName = trim(preg_replace('/[[:cntrl:]]/', '', $lines[$i]));
if (!empty($printerName)) {
$printers[$printerName] = $printerName;
}
}
}
} elseif ($os === 'DAR' || $os === 'LIN') {
// macOS and Linux implementation
$output = shell_exec('lpstat -p 2>/dev/null');
if ($output !== null) {
$lines = explode(PHP_EOL, trim($output));
foreach ($lines as $line) {
if (preg_match('/printer\s+(.*?)\s+is/', $line, $matches)) {
$printerName = trim($matches[1]);
if (!empty($printerName)) {
$printers[$printerName] = $printerName;
}
}
}
}
}
return $printers;
}
public function printImage(string $imagePath, string $printerName, int $copies = 1): bool
{
$os = strtoupper(substr(PHP_OS, 0, 3));
if ($os === 'WIN') {
// Windows implementation
$magickPath = base_path('bin/imagick/win64/magick.exe');
if (!file_exists($magickPath)) {
Log::error('PrinterService: ImageMagick executable not found.', ['path' => $magickPath]);
return false;
}
$success = true;
for ($i = 0; $i < $copies; $i++) {
// Ensure imagePath is absolute and properly quoted
$quotedImagePath = escapeshellarg($imagePath);
$quotedPrinterName = escapeshellarg("printer:$printerName"); // "printer:printerName" needs to be one argument
$command = "{$magickPath} {$quotedImagePath} {$quotedPrinterName}";
Log::debug('PrinterService: Executing print command:', ['command' => $command]);
$output = shell_exec($command);
// Log::debug('PrinterService: Executing print command:', ['command' => $command]);
$output = shell_exec($command);
Log::debug('PrinterService: Print command output:', ['output' => $output]);
// Check for errors. shell_exec returns NULL on command failure.
if ($output === null) {
Log::error('PrinterService: Print command failed.', ['command' => $command, 'output' => $output]);
$success = false;
break; // Stop if one copy fails
}
}
return $success;
} elseif ($os === 'DAR' || $os === 'LIN') {
// macOS and Linux implementation
if (!file_exists($imagePath)) {
Log::error('PrinterService: Image file not found.', ['path' => $imagePath]);
return false;
}
// Use the lp command to print the image
$success = true;
for ($i = 0; $i < $copies; $i++) {
$quotedImagePath = escapeshellarg($imagePath);
$quotedPrinterName = escapeshellarg($printerName);
// For image printing on Linux/macOS, we might need to convert to a printable format first
// Using lp command directly with the image file
$command = "lp -d {$quotedPrinterName} {$quotedImagePath} 2>&1";
Log::debug('PrinterService: Executing print command:', ['command' => $command]);
$output = shell_exec($command);
Log::debug('PrinterService: Print command output:', ['output' => $output]);
// Check for errors
if ($output === null || (strpos($output, 'Error') !== false)) {
Log::error('PrinterService: Print command failed.', ['command' => $command, 'output' => $output]);
$success = false;
break; // Stop if one copy fails
}
}
return $success;
} else {
Log::error('PrinterService: Unsupported operating system.');
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
ImageMagick License
https://imagemagick.org/script/license.php
Before we get to the text of the license, lets just review what the license says in simple terms:
It allows you to:
* freely download and use ImageMagick software, in whole or in part, for personal, company internal, or commercial purposes;
* use ImageMagick software in packages or distributions that you create;
* link against a library under a different license;
* link code under a different license against a library under this license;
* merge code into a work under a different license;
* extend patent grants to any code using code under this license;
* and extend patent protection.
It forbids you to:
* redistribute any piece of ImageMagick-originated software without proper attribution;
* use any marks owned by ImageMagick Studio LLC in any way that might state or imply that ImageMagick Studio LLC endorses your distribution;
* use any marks owned by ImageMagick Studio LLC in any way that might state or imply that you created the ImageMagick software in question.
It requires you to:
* include a copy of the license in any redistribution you may make that includes ImageMagick software;
* provide clear attribution to ImageMagick Studio LLC for any distributions that include ImageMagick software.
It does not require you to:
* include the source of the ImageMagick software itself, or of any modifications you may have made to it, in any redistribution you may assemble that includes it;
* submit changes that you make to the software back to the ImageMagick Studio LLC (though such feedback is encouraged).
A few other clarifications include:
* ImageMagick is freely available without charge;
* you may include ImageMagick on a DVD as long as you comply with the terms of the license;
* you can give modified code away for free or sell it under the terms of the ImageMagick license or distribute the result under a different license, but you need to acknowledge the use of the ImageMagick software;
* the license is compatible with the GPL V3.
* when exporting the ImageMagick software, review its export classification.
Terms and Conditions for Use, Reproduction, and Distribution
The legally binding and authoritative terms and conditions for use, reproduction, and distribution of ImageMagick follow:
Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization dedicated to making software imaging solutions freely available.
1. Definitions.
License shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
Legal Entity shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, control means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
You (or Your) shall mean an individual or Legal Entity exercising permissions granted by this License.
Source form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
Object form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
Work shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
Derivative Works shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
Contribution shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as Not a Contribution.
Contributor shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
* You must give any other recipients of the Work or Derivative Works a copy of this License; and
* You must cause any modified files to carry prominent notices stating that You changed the files; and
* You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
* If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
How to Apply the License to your Work
To apply the ImageMagick License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information (don't include the brackets). The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the ImageMagick License (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy
of the License at
https://imagemagick.org/script/license.php
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.

7245
bin/imagick/win64/NOTICE.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE colormap [
<!ELEMENT colormap (color)*>
<!ELEMENT color (#PCDATA)>
<!ATTLIST color name CDATA "0">
<!ATTLIST color color CDATA "rgb(0,0,0)">
<!ATTLIST color compliance CDATA "SVG">
]>
<!--
Associate a color name with its red, green, blue, and alpha intensities.
A number of methods and options require a color parameter. It is often
convenient to refer to a color by name (e.g. white) rather than by hex
value (e.g. #fff). This file maps a color name to its equivalent red,
green, blue, and alpha intensities (e.g. for white, red = 255, green =
255, blue = 255, and alpha = 0).
-->
<colormap>
<!-- <color name="none" color="rgba(0,0,0,0)" compliance="SVG, XPM"/> -->
<!-- <color name="black" color="rgb(0,0,0)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="red" color="rgb(255,0,0)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="magenta" color="rgb(255,0,255)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="green" color="rgb(0,128,0)" compliance="SVG"/> -->
<!-- <color name="cyan" color="rgb(0,255,255)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="blue" color="rgb(0,0,255)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="yellow" color="rgb(255,255,0)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="white" color="rgb(255,255,255)" compliance="SVG, X11"/> -->
</colormap>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuremap [
<!ELEMENT configuremap (configure)+>
<!ATTLIST configuremap xmlns CDATA #FIXED ''>
<!ELEMENT configure EMPTY>
<!ATTLIST configure xmlns CDATA #FIXED '' name NMTOKEN #REQUIRED
value CDATA #REQUIRED>
]>
<!--
ImageMagick build configuration.
-->
<configuremap>
<configure name="CC" value="VS2022"/>
<configure name="CHANNEL_MASK_DEPTH" value="64"/>
<configure name="COPYRIGHT" value="Copyright (C) 1999 ImageMagick Studio LLC"/>
<configure name="CXX" value="VS2022"/>
<configure name="DOCUMENTATION_PATH" value="unavailable"/>
<configure name="GIT_REVISION" value="83b6fc3:20250811" />
<configure name="LIB_VERSION_NUMBER" value="7,1,2,1"/>
<configure name="LIB_VERSION" value="0x712"/>
<configure name="NAME" value="ImageMagick"/>
<configure name="QuantumDepth" value="16"/>
<configure name="RELEASE_DATE" value="2025-08-12"/>
<configure name="TARGET_CPU" value="x64"/>
<configure name="TARGET_OS" value="Windows"/>
<configure name="VERSION" value="7.1.2"/>
<configure name="WEBSITE" value="https://imagemagick.org"/>
</configuremap>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE delegatemap [
<!ELEMENT delegatemap (delegate)+>
<!ATTLIST delegatemap xmlns CDATA #FIXED ''>
<!ELEMENT delegate EMPTY>
<!ATTLIST delegate xmlns CDATA #FIXED '' command CDATA #REQUIRED
decode NMTOKEN #IMPLIED encode NMTOKEN #IMPLIED mode NMTOKEN #IMPLIED
spawn NMTOKEN #IMPLIED stealth NMTOKEN #IMPLIED>
]>
<!--
Delegate command file.
Commands which specify
decode="in_format" encode="out_format"
specify the rules for converting from in_format to out_format. Use these
rules to translate directly between formats.
Commands which specify only
decode="in_format"
specify the rules for converting from in_format to some format that
ImageMagick automatically recognizes. Use these rules to decode formats.
Commands which specify only
encode="out_format"
specify the rules for an "encoder" which may accept any input format.
The substitution rules are as follows:
%a authentication passphrase
%b image file size in bytes
%g image geometry
%h image rows (height)
%i input image filename
%# input image signature
%m input image format
%o output image filename
%p page number
%q input image depth
%s scene number
%u unique temporary filename
%w image columns (width)
%x input image x resolution
%y input image y resolution
%Q input image compression quality
Set option delegate:bimodal=true to process bimodal delegates otherwise they
are ignored.
If stealth="True" the delegate is not listed in user requested
"-list delegate" listings. These are typically special internal delegates.
If spawn="True", ImageMagick does not wait for the delegate to finish, nor
will it read any output image.
-->
<delegatemap>
<delegate decode="bpg" command="&quot;bpgdec&quot; -b 16 -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="png" encode="bpg" command="&quot;bpgenc&quot; -b 12 -q &quot;%~&quot; -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="dng:decode" stealth="True" command="dcraw.exe -6 -W -O &quot;%u.ppm&quot; &quot;%i&quot;"/>
<delegate decode="dot" command="&quot;dot&quot; -Tsvg &quot;%i&quot; -o &quot;%o&quot;" />
<delegate decode="dvi" command="&quot;dvips&quot; -q -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="eps" encode="pdf" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 &quot;-sDEVICE=pdfwrite&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
<delegate decode="eps" encode="ps" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=ps2write&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
<delegate decode="hpg" command="&quot;hp2xx&quot; -q -m eps -f &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="hpgl" command="&quot;hp2xx&quot; -q -m eps -f &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="htm" command="&quot;html2ps&quot; -U -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="html" command="&quot;html2ps&quot; -U -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="ilbm" command="&quot;ilbmtoppm&quot; &quot;%i&quot; &gt; &quot;%o&quot;"/>
<delegate decode="jpg" encode="lep" mode="encode" command="&quot;lepton&quot; &quot;%i&quot; &quot;%o&quot;"/>
<delegate decode="jxr" command="cmd.exe /c (move &quot;%i&quot; &quot;%i.jxr&quot; >nul) &amp; (&quot;JXRDecApp.exe&quot; -i &quot;%i.jxr&quot; -o &quot;%o.tiff&quot;) &amp; (move &quot;%i.jxr&quot; &quot;%i&quot; >nul) &amp; (move &quot;%o.tiff&quot; &quot;%o&quot; >nul)"/>
<delegate decode="lep" mode="decode" command="&quot;lepton&quot; &quot;%i&quot; &quot;%o&quot;"/>
<delegate decode="pcl:cmyk" stealth="True" command="&quot;pcl6&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pamcmyk32&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="pcl:color" stealth="True" command="&quot;pcl6&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=ppmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="pcl:mono" stealth="True" command="&quot;pcl6&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pbmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="pdf" encode="eps" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=eps2write&quot; &quot;-sPDFPassword=%a&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
<delegate decode="pdf" encode="ps" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=ps2write&quot; &quot;-sPDFPassword=%a&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
<delegate decode="pnm" encode="ilbm" mode="encode" command="&quot;ppmtoilbm&quot; -24if &quot;%i&quot; &gt; &quot;%o&quot;"/>
<delegate decode="tiff" encode="jxr" command="cmd.exe /c (move &quot;%i&quot; &quot;%i.tiff&quot; >nul) &amp; (&quot;JXREncApp.exe&quot; -i &quot;%i.tiff&quot; -o &quot;%o.jxr&quot; -q %Q) &amp; (move &quot;%i.tiff&quot; &quot;%i&quot; >nul) &amp; (move &quot;%o.jxr&quot; &quot;%o&quot; >nul)"/>
<delegate decode="tiff" encode="wdp" command="cmd.exe /c (move &quot;%i&quot; &quot;%i.tiff&quot; >nul) &amp; (&quot;JXREncApp.exe&quot; -i &quot;%i.tiff&quot; -o &quot;%o.jxr&quot; -q %Q) &amp; (move &quot;%i.tiff&quot; &quot;%i&quot; >nul) &amp; (move &quot;%o.jxr&quot; &quot;%o&quot; >nul)"/>
<delegate decode="ps:alpha" stealth="True" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pngalpha&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
<delegate decode="ps:cmyk" stealth="True" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pamcmyk32&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
<delegate decode="ps:color" stealth="True" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=png16m&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
<delegate decode="ps" encode="eps" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=eps2write&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
<delegate decode="ps" encode="pdf" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pdfwrite&quot; &quot;-sOutputFile=%o&quot; &quot;-f%i&quot;"/>
<delegate decode="ps:mono" stealth="True" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pbmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
<delegate decode="shtml" command="&quot;html2ps&quot; -U -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="svg" command="&quot;rsvg-convert&quot; --dpi-x %x --dpi-y %y -o &quot;%o&quot; &quot;%i&quot;"/>
<!-- Change export-filename to export-png for inkscape < 1.0 -->
<delegate decode="svg:decode" stealth="True" command="&quot;inkscape&quot; &quot;%s&quot; &quot;--export-filename=%s&quot; &quot;--export-dpi=%s&quot; &quot;--export-background=%s&quot; &quot;--export-background-opacity=%s&quot;"/>
<delegate decode="wdp" command="cmd.exe /c (move &quot;%i&quot; &quot;%i.jxr&quot; >nul) &amp; (&quot;JXRDecApp.exe&quot; -i &quot;%i.jxr&quot; -o &quot;%o.pnm&quot;) &amp; (move &quot;%i.jxr&quot; &quot;%i&quot; >nul) &amp; (move &quot;%o.pnm&quot; &quot;%o&quot; >nul)"/>
<delegate decode="xps:cmyk" stealth="True" command="&quot;gxps&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=bmpsep8&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="xps:color" stealth="True" command="&quot;gxps&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=ppmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="xps:mono" stealth="True" command="&quot;gxps&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pbmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="video:decode" command="&quot;ffmpeg&quot; -nostdin -loglevel error -i &quot;%s&quot; -an -f rawvideo -y %s &quot;%s&quot;"/>
<delegate encode="video:encode" stealth="True" command="&quot;ffmpeg&quot; -nostdin -loglevel error -i &quot;%s%%d.%s&quot; %s &quot;%s.%s&quot;"/>
</delegatemap>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE localemap [
<!ELEMENT localemap (include)+>
<!ATTLIST localemap xmlns CDATA #FIXED ''>
<!ELEMENT include EMPTY>
<!ATTLIST include xmlns CDATA #FIXED '' file NMTOKEN #REQUIRED
locale NMTOKEN #REQUIRED>
]>
<localemap>
<include locale="no_NO.ISO-8859-1" file="bokmal.xml"/>
<include locale="ca_ES.ISO-8859-1" file="catalan.xml"/>
<include locale="hr_HR.ISO-8859-2" file="croatian.xml"/>
<include locale="cs_CZ.ISO-8859-2" file="czech.xml"/>
<include locale="da_DK.ISO-8859-1" file="danish.xml"/>
<include locale="de_DE.ISO-8859-1" file="deutsch.xml"/>
<include locale="nl_NL.ISO-8859-1" file="dutch.xml"/>
<include locale="C" file="english.xml"/>
<include locale="et_EE.ISO-8859-1" file="estonian.xml"/>
<include locale="fi_FI.ISO-8859-1" file="finnish.xml"/>
<include locale="fr_FR.ISO-8859-1" file="francais.xml"/>
<include locale="fr_FR.ISO-8859-1" file="francais.xml"/>
<include locale="fr_FR.UTF-8" file="francais.xml"/>
<include locale="gl_ES.ISO-8859-1" file="galego.xml"/>
<include locale="gl_ES.ISO-8859-1" file="galician.xml"/>
<include locale="de_DE.ISO-8859-1" file="german.xml"/>
<include locale="el_GR.ISO-8859-7" file="greek.xml"/>
<include locale="en_US.UTF-8" file="english.xml"/>
<include locale="iw_IL.ISO-8859-8" file="hebrew.xml"/>
<include locale="hr_HR.ISO-8859-2" file="hrvatski.xml"/>
<include locale="hu_HU.ISO-8859-2" file="hungarian.xml"/>
<include locale="is_IS.ISO-8859-1" file="icelandic.xml"/>
<include locale="it_IT.ISO-8859-1" file="italian.xml"/>
<include locale="ja_JP.eucJP" file="japanese.xml"/>
<include locale="ko_KR.eucKR" file="korean.xml"/>
<include locale="lt_LT.ISO-8859-13" file="lithuanian.xml"/>
<include locale="no_NO.ISO-8859-1" file="norwegian.xml"/>
<include locale="nn_NO.ISO-8859-1" file="nynorsk.xml"/>
<include locale="pl_PL.ISO-8859-2" file="polish.xml"/>
<include locale="pt_PT.ISO-8859-1" file="portuguese.xml"/>
<include locale="ro_RO.ISO-8859-2" file="romanian.xml"/>
<include locale="ru_RU.ISO-8859-5" file="russian.xml"/>
<include locale="sk_SK.ISO-8859-2" file="slovak.xml"/>
<include locale="sl_SI.ISO-8859-2" file="slovene.xml"/>
<include locale="es_ES.ISO-8859-1" file="spanish.xml"/>
<include locale="sv_SE.ISO-8859-1" file="swedish.xml"/>
<include locale="th_TH.TIS-620" file="thai.xml"/>
<include locale="tr_TR.ISO-8859-9" file="turkish.xml"/>
</localemap>

79
bin/imagick/win64/log.xml Normal file
View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logmap [
<!ELEMENT logmap (log)+>
<!ELEMENT log (#PCDATA)>
<!ATTLIST log events CDATA #IMPLIED>
<!ATTLIST log output CDATA #IMPLIED>
<!ATTLIST log filename CDATA #IMPLIED>
<!ATTLIST log generations CDATA #IMPLIED>
<!ATTLIST log limit CDATA #IMPLIED>
<!ATTLIST log format CDATA #IMPLIED>
]>
<!--
Configure ImageMagick logger.
Choose from one or more these events separated by a comma:
all
accelerate
annotate
blob
cache
coder
command
configure
deprecate
draw
exception
locale
module
none
pixel
policy
resource
trace
transform
user
wand
x11
Choose one output handler:
console
debug
event
file
none
stderr
stdout
When output is to a file, specify the filename. Embed %g in the filename to
support log generations. Generations is the number of log files to retain.
Limit is the number of logging events before generating a new log generation.
The format of the log is defined by embedding special format characters:
%c client
%d domain
%e event
%f function
%i thread id
%l line
%m module
%n log name
%p process id
%r real CPU time
%t wall clock time
%u user CPU time
%v version
%% percent sign
\n newline
\r carriage return
xml
-->
<logmap>
<log events="None"/>
<log output="console"/>
<log filename="Magick-%g.log"/>
<log generations="3"/>
<log limit="2GiB"/>
<log format="%t %r %u %v %d %c[%p]: %m/%f/%l/%d\n %e"/>
</logmap>

Binary file not shown.

1157
bin/imagick/win64/mime.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
<!ELEMENT policymap (policy)*>
<!ATTLIST policymap xmlns CDATA #FIXED "">
<!ELEMENT policy EMPTY>
<!ATTLIST policy xmlns CDATA #FIXED "">
<!ATTLIST policy domain NMTOKEN #REQUIRED>
<!ATTLIST policy name NMTOKEN #IMPLIED>
<!ATTLIST policy pattern CDATA #IMPLIED>
<!ATTLIST policy rights NMTOKEN #IMPLIED>
<!ATTLIST policy stealth NMTOKEN #IMPLIED>
<!ATTLIST policy value CDATA #IMPLIED>
]>
<!--
Creating a security policy that fits your specific local environment
before making use of ImageMagick is highly advised. You can find guidance on
setting up this policy at https://imagemagick.org/script/security-policy.php,
and it's important to verify your policy using the validation tool located
at https://imagemagick-secevaluator.doyensec.com/.
Open ImageMagick security policy:
The default policy for ImageMagick installations is the open security
policy. This policy is designed for usage in secure settings like those
protected by firewalls or within Docker containers. Within this framework,
ImageMagick enjoys broad access to resources and functionalities. This policy
provides convenient and adaptable options for image manipulation. However,
it's important to note that it might present security vulnerabilities in
less regulated conditions. Thus, organizations should thoroughly assess
the appropriateness of the open policy according to their particular use
case and security prerequisites.
ImageMagick security policies in a nutshell:
Domains include system, delegate, coder, filter, module, path, or resource.
Rights include none, read, write, execute and all. Use | to combine them,
for example: "read | write" to permit read from, or write to, a path.
Use a glob expression as a pattern.
Suppose we do not want users to process MPEG video images, use this policy:
<policy domain="delegate" rights="none" pattern="mpeg:decode" />
Here we do not want users reading images from HTTP:
<policy domain="coder" rights="none" pattern="HTTP" />
The /repository file system is restricted to read only. We use a glob
expression to match all paths that start with /repository:
<policy domain="path" rights="read" pattern="/repository/*" />
Prevent users from executing any image filters:
<policy domain="filter" rights="none" pattern="*" />
Cache large images to disk rather than memory:
<policy domain="resource" name="area" value="1GP"/>
Use the default system font unless overridden by the application:
<policy domain="system" name="font" value="/usr/share/fonts/favorite.ttf"/>
Define arguments for the memory, map, area, width, height and disk resources
with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
exceeds policy maximum so memory limit is 1GB).
Rules are processed in order. Here we want to restrict ImageMagick to only
read or write a small subset of proven web-safe image types:
<policy domain="delegate" rights="none" pattern="*" />
<policy domain="filter" rights="none" pattern="*" />
<policy domain="coder" rights="none" pattern="*" />
<policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
See https://imagemagick.org/script/security-policy.php for a deeper
understanding of ImageMagick security policies.
-->
<policymap>
<policy domain="Undefined" rights="none"/>
<!-- Set maximum parallel threads. -->
<!-- <policy domain="resource" name="thread" value="2"/> -->
<!-- Set maximum time to live in seconds or mnemonics, e.g. "2 minutes". When
this limit is exceeded, an exception is thrown and processing stops. -->
<!-- <policy domain="resource" name="time" value="120"/> -->
<!-- Set maximum number of open pixel cache files. When this limit is
exceeded, any subsequent pixels cached to disk are closed and reopened
on demand. -->
<!-- <policy domain="resource" name="file" value="768"/> -->
<!-- Set maximum amount of memory in bytes to allocate for the pixel cache
from the heap. When this limit is exceeded, the image pixels are cached
to memory-mapped disk. -->
<!-- <policy domain="resource" name="memory" value="256MiB"/> -->
<!-- Set maximum amount of memory map in bytes to allocate for the pixel
cache. When this limit is exceeded, the image pixels are cached to
disk. -->
<!-- <policy domain="resource" name="map" value="512MiB"/> -->
<!-- Set the maximum width * height of an image that can reside in the pixel
cache memory. Images that exceed the area limit are cached to disk. -->
<!-- <policy domain="resource" name="area" value="16KP"/> -->
<!-- Set maximum amount of disk space in bytes permitted for use by the pixel
cache. When this limit is exceeded, the pixel cache is not be created
and an exception is thrown. -->
<!-- <policy domain="resource" name="disk" value="1GiB"/> -->
<!-- Set the maximum length of an image sequence. When this limit is
exceeded, an exception is thrown. -->
<!-- <policy domain="resource" name="list-length" value="32"/> -->
<!-- Set the maximum width of an image. When this limit is exceeded, an
exception is thrown. -->
<!-- <policy domain="resource" name="width" value="8KP"/> -->
<!-- Set the maximum height of an image. When this limit is exceeded, an
exception is thrown. -->
<!-- <policy domain="resource" name="height" value="8KP"/> -->
<!-- Periodically yield the CPU for at least the time specified in
milliseconds. -->
<!-- <policy domain="resource" name="throttle" value="2"/> -->
<!-- Do not create temporary files in the default shared directories, instead
specify a private area to store only ImageMagick temporary files. -->
<!-- <policy domain="resource" name="temporary-path" value="/magick/tmp/"/> -->
<!-- Force memory initialization by memory mapping select memory
allocations. -->
<!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
<!-- Ensure all image data is fully flushed and synchronized to disk. -->
<!-- <policy domain="cache" name="synchronize" value="true"/> -->
<!-- Replace passphrase for secure distributed processing -->
<!-- <policy domain="cache" name="shared-secret" value="secret-passphrase" stealth="true"/> -->
<!-- Do not permit any delegates to execute. -->
<!-- <policy domain="delegate" rights="none" pattern="*"/> -->
<!-- Do not permit any image filters to load. -->
<!-- <policy domain="filter" rights="none" pattern="*"/> -->
<!-- Don't read/write from/to stdin/stdout. -->
<!-- <policy domain="path" rights="none" pattern="-"/> -->
<!-- don't read sensitive paths. -->
<!-- <policy domain="path" rights="none" pattern="/etc/*"/> -->
<!-- Indirect reads are not permitted. -->
<!-- <policy domain="path" rights="none" pattern="@*"/> -->
<!-- These image types are security risks on read, but write is fine -->
<!-- <policy domain="module" rights="write" pattern="{MSL,MVG,PS,SVG,URL,XPS}"/> -->
<!-- This policy sets the number of times to replace content of certain
memory buffers and temporary files before they are freed or deleted. -->
<!-- <policy domain="system" name="shred" value="1"/> -->
<!-- Enable the initialization of buffers with zeros, resulting in a minor
performance penalty but with improved security. -->
<!-- <policy domain="system" name="memory-map" value="anonymous"/> -->
<!-- Set the maximum amount of memory in bytes that are permitted for
allocation requests. -->
<!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
</policymap>

BIN
bin/imagick/win64/sRGB.icc Normal file

Binary file not shown.

View File

@@ -0,0 +1,336 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE thresholds [
<!ELEMENT thresholds (threshold)+>
<!ATTLIST thresholds xmlns CDATA #FIXED ''>
<!ELEMENT threshold (description,levels)>
<!ATTLIST threshold xmlns CDATA #FIXED '' alias NMTOKEN #IMPLIED
map NMTOKEN #REQUIRED>
<!ELEMENT description (#PCDATA)>
<!ATTLIST description xmlns CDATA #FIXED ''>
<!ELEMENT levels (#PCDATA)>
<!ATTLIST levels xmlns CDATA #FIXED '' divisor CDATA #REQUIRED
height CDATA #REQUIRED width CDATA #REQUIRED>
]>
<!--
Threshold Maps for Ordered Posterized Dither
Each "<threshold>" element defines the map name, description, and an array
of "levels" used to provide the threshold map for ordered dithering and
digital halftoning.
The "alias" attribute provides a backward compatible name for this threshold
map (pre-dating IM v6.2.9-6), and are deprecated.
The description is a english description of what the threshold map achieves
and is only used for 'listing' the maps.
The map itself is a rectangular array of integers or threshold "levels"
of the given "width" and "height" declared within the enclosing <levels>
element. That is "width*height" integers or "levels" *must* be provided
within each map.
Each of the "levels" integer values (each value representing the threshold
intensity "level/divisor" at which that pixel is turned on. The "levels"
integers given can be any positive integers between "0" and the "divisor",
excluding those limits.
The "divisor" not only defines the upper limit and threshold divisor for each
"level" but also the total number of pseudo-levels the threshold mapping
creates and fills with a dither pattern. That is a ordered bitmap dither
of a pure greyscale gradient will use a maximum of "divisor" ordered bitmap
patterns, including the patterns with all the pixels 'on' and all the pixel
'off'. It may define less patterns than that, but the color channels will
be thresholded in units based on "divisor".
Alternatively for a multi-level posterization, ImageMagick inserts
"divisor-2" dither patterns (as defined by the threshold map) between each of
channel color level produced.
For example the map "o2x2" has a divisor of 5, which will define 3 bitmap
patterns plus the patterns with all pixels 'on' and 'off'. A greyscale
gradient will thus have 5 distinct areas.
-->
<thresholds>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Minimal Dither and Non-Dither Threshold Maps
-->
<threshold map="threshold" alias="1x1">
<description>Threshold 1x1 (non-dither)</description>
<levels width="1" height="1" divisor="2">
1
</levels>
</threshold>
<threshold map="checks" alias="2x1">
<description>Checkerboard 2x1 (dither)</description>
<levels width="2" height="2" divisor="3">
1 2
2 1
</levels>
</threshold>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(dispersed) Ordered Dither Patterns
-->
<threshold map="o2x2" alias="2x2">
<description>Ordered 2x2 (dispersed)</description>
<levels width="2" height="2" divisor="5">
1 3
4 2
</levels>
</threshold>
<threshold map="o3x3" alias="3x3">
<description>Ordered 3x3 (dispersed)</description>
<levels width="3" height="3" divisor="10">
3 7 4
6 1 9
2 8 5
</levels>
</threshold>
<threshold map="o4x4" alias="4x4">
<!--
From "Dithering Algorithms"
http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
-->
<description>Ordered 4x4 (dispersed)</description>
<levels width="4" height="4" divisor="17">
1 9 3 11
13 5 15 7
4 12 2 10
16 8 14 6
</levels>
</threshold>
<threshold map="o8x8" alias="8x8">
<!-- Extracted from original 'OrderedDither()' Function -->
<description>Ordered 8x8 (dispersed)</description>
<levels width="8" height="8" divisor="65">
1 49 13 61 4 52 16 64
33 17 45 29 36 20 48 32
9 57 5 53 12 60 8 56
41 25 37 21 44 28 40 24
3 51 15 63 2 50 14 62
35 19 47 31 34 18 46 30
11 59 7 55 10 58 6 54
43 27 39 23 42 26 38 22
</levels>
</threshold>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Halftones - Angled 45 degrees
Initially added to ImageMagick by Glenn Randers-Pehrson, IM v6.2.8-6,
modified to be more symmetrical with intensity by Anthony, IM v6.2.9-7
These patterns initially start as circles, but then form diamonds
pattern at the 50% threshold level, before forming negated circles,
as it approached the other threshold extreme.
-->
<threshold map="h4x4a" alias="4x1">
<description>Halftone 4x4 (angled)</description>
<levels width="4" height="4" divisor="9">
4 2 7 5
3 1 8 6
7 5 4 2
8 6 3 1
</levels>
</threshold>
<threshold map="h6x6a" alias="6x1">
<description>Halftone 6x6 (angled)</description>
<levels width="6" height="6" divisor="19">
14 13 10 8 2 3
16 18 12 7 1 4
15 17 11 9 6 5
8 2 3 14 13 10
7 1 4 16 18 12
9 6 5 15 17 11
</levels>
</threshold>
<threshold map="h8x8a" alias="8x1">
<description>Halftone 8x8 (angled)</description>
<levels width="8" height="8" divisor="33">
13 7 8 14 17 21 22 18
6 1 3 9 28 31 29 23
5 2 4 10 27 32 30 24
16 12 11 15 20 26 25 19
17 21 22 18 13 7 8 14
28 31 29 23 6 1 3 9
27 32 30 24 5 2 4 10
20 26 25 19 16 12 11 15
</levels>
</threshold>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Halftones - Orthogonally Aligned, or Un-angled
Initially added by Anthony Thyssen, IM v6.2.9-5 using techniques from
"Dithering & Halftoning" by Gernot Hoffmann
http://www.fho-emden.de/~hoffmann/hilb010101.pdf
These patterns initially start as circles, but then form square
pattern at the 50% threshold level, before forming negated circles,
as it approached the other threshold extreme.
-->
<threshold map="h4x4o">
<description>Halftone 4x4 (orthogonal)</description>
<levels width="4" height="4" divisor="17">
7 13 11 4
12 16 14 8
10 15 6 2
5 9 3 1
</levels>
</threshold>
<threshold map="h6x6o">
<description>Halftone 6x6 (orthogonal)</description>
<levels width="6" height="6" divisor="37">
7 17 27 14 9 4
21 29 33 31 18 11
24 32 36 34 25 22
19 30 35 28 20 10
8 15 26 16 6 2
5 13 23 12 3 1
</levels>
</threshold>
<threshold map="h8x8o">
<description>Halftone 8x8 (orthogonal)</description>
<levels width="8" height="8" divisor="65">
7 21 33 43 36 19 9 4
16 27 51 55 49 29 14 11
31 47 57 61 59 45 35 23
41 53 60 64 62 52 40 38
37 44 58 63 56 46 30 22
15 28 48 54 50 26 17 10
8 18 34 42 32 20 6 2
5 13 25 39 24 12 3 1
</levels>
</threshold>
<threshold map="h16x16o">
<!--
Direct extract from "Dithering & Halftoning" by Gernot Hoffmann.
This may need some fine tuning for symmetry of the halftone dots,
as it was a mathematically formulated pattern.
-->
<description>Halftone 16x16 (orthogonal)</description>
<levels width="16" height="16" divisor="257">
4 12 24 44 72 100 136 152 150 134 98 70 42 23 11 3
7 16 32 52 76 104 144 160 158 142 102 74 50 31 15 6
19 27 40 60 92 132 168 180 178 166 130 90 58 39 26 18
36 48 56 80 124 176 188 204 203 187 175 122 79 55 47 35
64 68 84 116 164 200 212 224 223 211 199 162 114 83 67 63
88 96 112 156 192 216 232 240 239 231 214 190 154 111 95 87
108 120 148 184 208 228 244 252 251 243 226 206 182 147 119 107
128 140 172 196 219 235 247 256 255 246 234 218 194 171 139 127
126 138 170 195 220 236 248 253 254 245 233 217 193 169 137 125
106 118 146 183 207 227 242 249 250 241 225 205 181 145 117 105
86 94 110 155 191 215 229 238 237 230 213 189 153 109 93 85
62 66 82 115 163 198 210 221 222 209 197 161 113 81 65 61
34 46 54 78 123 174 186 202 201 185 173 121 77 53 45 33
20 28 37 59 91 131 167 179 177 165 129 89 57 38 25 17
8 13 29 51 75 103 143 159 157 141 101 73 49 30 14 5
1 9 21 43 71 99 135 151 149 133 97 69 41 22 10 2
</levels>
</threshold>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Halftones - Orthogonally Expanding Circle Patterns
Added by Glenn Randers-Pehrson, 4 Nov 2010, ImageMagick 6.6.5-6
Rather than producing a diamond 50% threshold pattern, these
continue to generate larger (overlapping) circles. They are
more like a true halftone pattern formed by covering a surface
with either pure white or pure black circular dots.
WARNING: true halftone patterns only use true circles even in
areas of highly varying intensity. Threshold dither patterns
can generate distorted circles in such areas.
-->
<threshold map="c5x5b" alias="c5x5">
<description>Circles 5x5 (black)</description>
<levels width="5" height="5" divisor="26">
1 21 16 15 4
5 17 20 19 14
6 21 25 24 12
7 18 22 23 11
2 8 9 10 3
</levels>
</threshold>
<threshold map="c5x5w">
<description>Circles 5x5 (white)</description>
<levels width="5" height="5" divisor="26">
25 21 10 11 22
20 9 6 7 12
19 5 1 2 13
18 8 4 3 14
24 17 16 15 23
</levels>
</threshold>
<threshold map="c6x6b" alias="c6x6">
<description>Circles 6x6 (black)</description>
<levels width="6" height="6" divisor="37">
1 5 14 13 12 4
6 22 28 27 21 11
15 29 35 34 26 20
16 30 36 33 25 19
7 23 31 32 24 10
2 8 17 18 9 3
</levels>
</threshold>
<threshold map="c6x6w">
<description>Circles 6x6 (white)</description>
<levels width="6" height="6" divisor="37">
36 32 23 24 25 33
31 15 9 10 16 26
22 8 2 3 11 17
21 7 1 4 12 18
30 14 6 5 13 27
35 29 20 19 28 34
</levels>
</threshold>
<threshold map="c7x7b" alias="c7x7">
<description>Circles 7x7 (black)</description>
<levels width="7" height="7" divisor="50">
3 9 18 28 17 8 2
10 24 33 39 32 23 7
19 34 44 48 43 31 16
25 40 45 49 47 38 27
20 35 41 46 42 29 15
11 21 36 37 28 22 6
4 12 13 26 14 5 1
</levels>
</threshold>
<threshold map="c7x7w">
<description>Circles 7x7 (white)</description>
<levels width="7" height="7" divisor="50">
47 41 32 22 33 42 48
40 26 17 11 18 27 43
31 16 6 2 7 19 34
25 10 5 1 3 12 23
30 15 9 4 8 20 35
39 29 14 13 21 28 44
46 38 37 24 36 45 49
</levels>
</threshold>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Special Purpose Dithers
-->
</thresholds>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE typemap [
<!ELEMENT typemap (type)+>
<!ELEMENT type (#PCDATA)>
<!ELEMENT include (#PCDATA)>
<!ATTLIST type name CDATA #REQUIRED>
<!ATTLIST type fullname CDATA #IMPLIED>
<!ATTLIST type family CDATA #IMPLIED>
<!ATTLIST type foundry CDATA #IMPLIED>
<!ATTLIST type weight CDATA #IMPLIED>
<!ATTLIST type style CDATA #IMPLIED>
<!ATTLIST type stretch CDATA #IMPLIED>
<!ATTLIST type format CDATA #IMPLIED>
<!ATTLIST type metrics CDATA #IMPLIED>
<!ATTLIST type glyphs CDATA #REQUIRED>
<!ATTLIST type version CDATA #IMPLIED>
<!ATTLIST include file CDATA #REQUIRED>
]>
<typemap>
<type name="AvantGarde-Book" fullname="AvantGarde Book" family="AvantGarde" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@a010013l.afm" glyphs="@ghostscript_font_path@a010013l.pfb"/>
<type name="AvantGarde-BookOblique" fullname="AvantGarde Book Oblique" family="AvantGarde" foundry="URW" weight="400" style="oblique" stretch="normal" format="type1" metrics="@ghostscript_font_path@a010033l.afm" glyphs="@ghostscript_font_path@a010033l.pfb"/>
<type name="AvantGarde-Demi" fullname="AvantGarde DemiBold" family="AvantGarde" foundry="URW" weight="600" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@a010015l.afm" glyphs="@ghostscript_font_path@a010015l.pfb"/>
<type name="AvantGarde-DemiOblique" fullname="AvantGarde DemiOblique" family="AvantGarde" foundry="URW" weight="600" style="oblique" stretch="normal" format="type1" metrics="@ghostscript_font_path@a010035l.afm" glyphs="@ghostscript_font_path@a010035l.pfb"/>
<type name="Bookman-Demi" fullname="Bookman DemiBold" family="Bookman" foundry="URW" weight="600" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@b018015l.afm" glyphs="@ghostscript_font_path@b018015l.pfb"/>
<type name="Bookman-DemiItalic" fullname="Bookman DemiBold Italic" family="Bookman" foundry="URW" weight="600" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@b018035l.afm" glyphs="@ghostscript_font_path@b018035l.pfb"/>
<type name="Bookman-Light" fullname="Bookman Light" family="Bookman" foundry="URW" weight="300" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@b018012l.afm" glyphs="@ghostscript_font_path@b018012l.pfb"/>
<type name="Bookman-LightItalic" fullname="Bookman Light Italic" family="Bookman" foundry="URW" weight="300" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@b018032l.afm" glyphs="@ghostscript_font_path@b018032l.pfb"/>
<type name="Fixed" fullname="Courier Regular" family="Courier" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@n022003l.afm" glyphs="@ghostscript_font_path@n022003l.pfb"/>
<type name="Courier" fullname="Courier Regular" family="Courier" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@n022003l.afm" glyphs="@ghostscript_font_path@n022003l.pfb"/>
<type name="Courier-Bold" fullname="Courier Bold" family="Courier" foundry="URW" weight="700" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@n022004l.afm" glyphs="@ghostscript_font_path@n022004l.pfb"/>
<type name="Courier-Oblique" fullname="Courier Regular Oblique" family="Courier" foundry="URW" weight="400" style="oblique" stretch="normal" format="type1" metrics="@ghostscript_font_path@n022023l.afm" glyphs="@ghostscript_font_path@n022023l.pfb"/>
<type name="Courier-BoldOblique" fullname="Courier Bold Oblique" family="Courier" foundry="URW" weight="700" style="oblique" stretch="normal" format="type1" metrics="@ghostscript_font_path@n022024l.afm" glyphs="@ghostscript_font_path@n022024l.pfb"/>
<type name="fixed" fullname="Helvetica Regular" family="Helvetica" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@n019003l.afm" glyphs="@ghostscript_font_path@n019003l.pfb"/>
<type name="Helvetica" fullname="Helvetica Regular" family="Helvetica" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@n019003l.afm" glyphs="@ghostscript_font_path@n019003l.pfb"/>
<type name="Helvetica-Bold" fullname="Helvetica Bold" family="Helvetica" foundry="URW" weight="700" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@n019004l.afm" glyphs="@ghostscript_font_path@n019004l.pfb"/>
<type name="Helvetica-Oblique" fullname="Helvetica Regular Italic" family="Helvetica" foundry="URW" weight="400" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@n019023l.afm" glyphs="@ghostscript_font_path@n019023l.pfb"/>
<type name="Helvetica-BoldOblique" fullname="Helvetica Bold Italic" family="Helvetica" foundry="URW" weight="700" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@n019024l.afm" glyphs="@ghostscript_font_path@n019024l.pfb"/>
<type name="Helvetica-Narrow" fullname="Helvetica Narrow" family="Helvetica Narrow" foundry="URW" weight="400" style="normal" stretch="condensed" format="type1" metrics="@ghostscript_font_path@n019043l.afm" glyphs="@ghostscript_font_path@n019043l.pfb"/>
<type name="Helvetica-Narrow-Oblique" fullname="Helvetica Narrow Oblique" family="Helvetica Narrow" foundry="URW" weight="400" style="oblique" stretch="condensed" format="type1" metrics="@ghostscript_font_path@n019063l.afm" glyphs="@ghostscript_font_path@n019063l.pfb"/>
<type name="Helvetica-Narrow-Bold" fullname="Helvetica Narrow Bold" family="Helvetica Narrow" foundry="URW" weight="700" style="normal" stretch="condensed" format="type1" metrics="@ghostscript_font_path@n019044l.afm" glyphs="@ghostscript_font_path@n019044l.pfb"/>
<type name="Helvetica-Narrow-BoldOblique" fullname="Helvetica Narrow Bold Oblique" family="Helvetica Narrow" foundry="URW" weight="700" style="oblique" stretch="condensed" format="type1" metrics="@ghostscript_font_path@n019064l.afm" glyphs="@ghostscript_font_path@n019064l.pfb"/>
<type name="NewCenturySchlbk-Roman" fullname="New Century Schoolbook" family="NewCenturySchlbk" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@c059013l.afm" glyphs="@ghostscript_font_path@c059013l.pfb"/>
<type name="NewCenturySchlbk-Italic" fullname="New Century Schoolbook Italic" family="NewCenturySchlbk" foundry="URW" weight="400" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@c059033l.afm" glyphs="@ghostscript_font_path@c059033l.pfb"/>
<type name="NewCenturySchlbk-Bold" fullname="New Century Schoolbook Bold" family="NewCenturySchlbk" foundry="URW" weight="700" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@c059016l.afm" glyphs="@ghostscript_font_path@c059016l.pfb"/>
<type name="NewCenturySchlbk-BoldItalic" fullname="New Century Schoolbook Bold Italic" family="NewCenturySchlbk" foundry="URW" weight="700" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@c059036l.afm" glyphs="@ghostscript_font_path@c059036l.pfb"/>
<type name="Palatino-Roman" fullname="Palatino Regular" family="Palatino" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@p052003l.afm" glyphs="@ghostscript_font_path@p052003l.pfb"/>
<type name="Palatino-Italic" fullname="Palatino Italic" family="Palatino" foundry="URW" weight="400" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@p052023l.afm" glyphs="@ghostscript_font_path@p052023l.pfb"/>
<type name="Palatino-Bold" fullname="Palatino Bold" family="Palatino" foundry="URW" weight="700" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@p052004l.afm" glyphs="@ghostscript_font_path@p052004l.pfb"/>
<type name="Palatino-BoldItalic" fullname="Palatino Bold Italic" family="Palatino" foundry="URW" weight="700" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@p052024l.afm" glyphs="@ghostscript_font_path@p052024l.pfb"/>
<type name="Times-Roman" fullname="Times Regular" family="Times" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@n021003l.afm" glyphs="@ghostscript_font_path@n021003l.pfb"/>
<type name="Times-Bold" fullname="Times Medium" family="Times" foundry="URW" weight="700" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@n021004l.afm" glyphs="@ghostscript_font_path@n021004l.pfb"/>
<type name="Times-Italic" fullname="Times Regular Italic" family="Times" foundry="URW" weight="400" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@n021023l.afm" glyphs="@ghostscript_font_path@n021023l.pfb"/>
<type name="Times-BoldItalic" fullname="Times Medium Italic" family="Times" foundry="URW" weight="700" style="italic" stretch="normal" format="type1" metrics="@ghostscript_font_path@n021024l.afm" glyphs="@ghostscript_font_path@n021024l.pfb"/>
<type name="Symbol" fullname="Symbol" family="Symbol" foundry="URW" weight="400" style="normal" stretch="normal" format="type1" metrics="@ghostscript_font_path@s050000l.afm" glyphs="@ghostscript_font_path@s050000l.pfb" version="0.1" encoding="AdobeCustom"/>
</typemap>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE typemap [
<!ELEMENT typemap (type)+>
<!ELEMENT type (#PCDATA)>
<!ELEMENT include (#PCDATA)>
<!ATTLIST type name CDATA #REQUIRED>
<!ATTLIST type fullname CDATA #IMPLIED>
<!ATTLIST type family CDATA #IMPLIED>
<!ATTLIST type foundry CDATA #IMPLIED>
<!ATTLIST type weight CDATA #IMPLIED>
<!ATTLIST type style CDATA #IMPLIED>
<!ATTLIST type stretch CDATA #IMPLIED>
<!ATTLIST type format CDATA #IMPLIED>
<!ATTLIST type metrics CDATA #IMPLIED>
<!ATTLIST type glyphs CDATA #REQUIRED>
<!ATTLIST type version CDATA #IMPLIED>
<!ATTLIST include file CDATA #REQUIRED>
]>
<typemap>
<include file="type-ghostscript.xml"/>
</typemap>

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('password_reset_tokens');
}
};

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('failed_jobs');
}
};

View File

@@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('roles');
}
};

View File

@@ -1,29 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->foreignId('role_id')->constrained()->default(2);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropForeign(['role_id']);
$table->dropColumn('role_id');
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('images', function (Blueprint $table) {
$table->id();
$table->string('path');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('images');
}
};

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('api_providers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('api_url');
$table->string('username')->nullable();
$table->string('password')->nullable();
$table->string('token')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('api_providers');
}
};

View File

@@ -1,29 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('ai_models', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('model_type');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ai_models');
}
};

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('styles', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('prompt');
$table->text('description');
$table->string('preview_image');
$table->foreignId('api_provider_id')->constrained();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('styles');
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('images', function (Blueprint $table) {
$table->uuid('uuid')->after('id')->nullable()->unique();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('images', function (Blueprint $table) {
$table->dropColumn('uuid');
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('api_providers', function (Blueprint $table) {
$table->string('plugin')->nullable()->after('token');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('api_providers', function (Blueprint $table) {
$table->dropColumn('plugin');
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ai_models', function (Blueprint $table) {
$table->string('model_id')->after('name');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('ai_models', function (Blueprint $table) {
$table->dropColumn('model_id');
});
}
};

View File

@@ -1,30 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('styles', function (Blueprint $table) {
$table->dropConstrainedForeignId('api_provider_id');
$table->foreignId('ai_model_id')->constrained()->after('preview_image');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('styles', function (Blueprint $table) {
$table->dropConstrainedForeignId('ai_model_id');
$table->foreignId('api_provider_id')->constrained();
});
}
};

View File

@@ -1,36 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ai_models', function (Blueprint $table) {
// Check if the column exists before trying to modify it
if (Schema::hasColumn('ai_models', 'api_provider_id')) {
$table->unsignedBigInteger('api_provider_id')->nullable()->change();
} else {
// If the column doesn't exist, add it as nullable
$table->foreignId('api_provider_id')->nullable()->constrained()->after('model_type');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('ai_models', function (Blueprint $table) {
// Revert logic if needed, but for this specific case, it might be complex
// to revert a nullable change or a column addition if data exists.
// Consider the implications of rolling back this specific migration.
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('ai_model_api_provider', function (Blueprint $table) {
$table->foreignId('ai_model_id')->constrained()->onDelete('cascade');
$table->foreignId('api_provider_id')->constrained()->onDelete('cascade');
$table->primary(['ai_model_id', 'api_provider_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ai_model_api_provider');
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('styles', function (Blueprint $table) {
$table->text('parameters')->nullable()->after('preview_image');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('styles', function (Blueprint $table) {
$table->dropColumn('parameters');
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('api_providers', function (Blueprint $table) {
$table->boolean('enabled')->default(true)->after('name');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('api_providers', function (Blueprint $table) {
$table->dropColumn('enabled');
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('styles', function (Blueprint $table) {
$table->boolean('enabled')->default(true)->after('ai_model_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('styles', function (Blueprint $table) {
$table->dropColumn('enabled');
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('settings', function (Blueprint $table) {
$table->string('key')->primary();
$table->text('value')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('settings');
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ai_models', function (Blueprint $table) {
$table->boolean('enabled')->default(true)->after('model_type');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('ai_models', function (Blueprint $table) {
$table->dropColumn('enabled');
});
}
};

View File

@@ -1,36 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('images', function (Blueprint $table) {
$table->unsignedBigInteger('original_image_id')->nullable()->after('id');
$table->foreign('original_image_id')->references('id')->on('images')->onDelete('set null');
$table->unsignedInteger('style_id')->nullable()->after('original_image_id');
$table->foreign('style_id')->references('id')->on('styles')->onDelete('set null');
$table->boolean('is_temp')->default(false)->after('style_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('images', function (Blueprint $table) {
$table->dropForeign(['original_image_id']);
$table->dropColumn('original_image_id');
$table->dropForeign(['style_id']);
$table->dropColumn('style_id');
$table->dropColumn('is_temp');
});
}
};

View File

@@ -1,30 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('email_notifications_enabled')->default(true);
$table->string('theme_preference')->default('light');
$table->string('locale')->default('en');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['email_notifications_enabled', 'theme_preference', 'locale']);
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('images', function (Blueprint $table) {
$table->boolean('is_public')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('images', function (Blueprint $table) {
$table->dropColumn('is_public');
});
}
};

View File

@@ -1,39 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->text('two_factor_secret')
->nullable();
$table->text('two_factor_recovery_codes')
->nullable();
$table->timestamp('two_factor_confirmed_at')
->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'two_factor_secret',
'two_factor_recovery_codes',
'two_factor_confirmed_at',
]);
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('images', function (Blueprint $table) {
$table->string('comfyui_prompt_id')->nullable()->after('uuid');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('images', function (Blueprint $table) {
$table->dropColumn('comfyui_prompt_id');
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ai_models', function (Blueprint $table) {
$table->json('parameters')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('ai_models', function (Blueprint $table) {
$table->dropColumn('parameters');
});
}
};

View File

@@ -1,45 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::rename('settings', 'settings_old');
Schema::create('settings', function (Blueprint $table) {
$table->id();
$table->string('key')->unique();
$table->text('value')->nullable();
$table->timestamps();
});
$oldSettings = DB::table('settings_old')->get();
foreach ($oldSettings as $setting) {
DB::table('settings')->insert((array)$setting);
}
Schema::drop('settings_old');
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('settings');
Schema::create('settings', function (Blueprint $table) {
$table->string('key')->primary();
$table->text('value')->nullable();
$table->timestamps();
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('styles', function (Blueprint $table) {
$table->integer('sort_order')->default(0)->after('enabled');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('styles', function (Blueprint $table) {
$table->dropColumn('sort_order');
});
}
};

View File

@@ -0,0 +1,145 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
$table->foreignId('role_id')->constrained()->default(2);
$table->boolean('email_notifications_enabled')->default(true);
$table->string('theme_preference')->default('light');
$table->string('locale')->default('en');
$table->text('two_factor_secret')->nullable();
$table->text('two_factor_recovery_codes')->nullable();
$table->timestamp('two_factor_confirmed_at')->nullable();
});
Schema::create('api_providers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->boolean('enabled')->default(true);
$table->string('api_url');
$table->string('username')->nullable();
$table->string('password')->nullable();
$table->string('token')->nullable();
$table->string('plugin')->nullable();
$table->timestamps();
});
Schema::create('ai_models', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('model_id');
$table->string('model_type');
$table->boolean('enabled')->default(true);
$table->foreignId('api_provider_id')->nullable()->constrained();
$table->json('parameters')->nullable();
$table->timestamps();
});
Schema::create('styles', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('prompt');
$table->text('description');
$table->string('preview_image');
$table->text('parameters')->nullable();
$table->foreignId('ai_model_id')->constrained();
$table->boolean('enabled')->default(true);
$table->integer('sort_order')->default(0);
$table->timestamps();
});
Schema::create('images', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->nullable()->unique();
$table->string('comfyui_prompt_id')->nullable();
$table->unsignedBigInteger('original_image_id')->nullable();
$table->foreign('original_image_id')->references('id')->on('images')->onDelete('set null');
$table->unsignedInteger('style_id')->nullable();
$table->foreign('style_id')->references('id')->on('styles')->onDelete('set null');
$table->boolean('is_temp')->default(false);
$table->string('path');
$table->boolean('is_public')->default(false);
$table->timestamps();
});
Schema::create('settings', function (Blueprint $table) {
$table->id();
$table->string('key')->unique();
$table->text('value')->nullable();
$table->timestamps();
});
Schema::create('ai_model_api_provider', function (Blueprint $table) {
$table->foreignId('ai_model_id')->constrained()->onDelete('cascade');
$table->foreignId('api_provider_id')->constrained()->onDelete('cascade');
$table->primary(['ai_model_id', 'api_provider_id']);
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
Schema::dropIfExists('failed_jobs');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('ai_model_api_provider');
Schema::dropIfExists('settings');
Schema::dropIfExists('images');
Schema::dropIfExists('styles');
Schema::dropIfExists('ai_models');
Schema::dropIfExists('api_providers');
Schema::dropIfExists('users');
Schema::dropIfExists('roles');
}
};

View File

@@ -13,7 +13,7 @@
</svg> </svg>
Schließen Schließen
</button> </button>
<button @click="$emit('print', image)" class="context-menu-button"> <button v-if="settings.show_print_button === '1'" @click="$emit('print', image)" class="context-menu-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" class="w-8 h-8 inline-block align-middle mr-2"><!--!Font Awesome Free v6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M128 0C92.7 0 64 28.7 64 64l0 96 64 0 0-96 226.7 0L384 93.3l0 66.7 64 0 0-66.7c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0L128 0zM384 352l0 32 0 64-256 0 0-64 0-16 0-16 256 0zm64 32l32 0c17.7 0 32-14.3 32-32l0-96c0-35.3-28.7-64-64-64L64 192c-35.3 0-64 28.7-64 64l0 96c0 17.7 14.3 32 32 32l32 0 0 64c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-64zM432 248a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" class="w-8 h-8 inline-block align-middle mr-2"><!--!Font Awesome Free v6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M128 0C92.7 0 64 28.7 64 64l0 96 64 0 0-96 226.7 0L384 93.3l0 66.7 64 0 0-66.7c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0L128 0zM384 352l0 32 0 64-256 0 0-64 0-16 0-16 256 0zm64 32l32 0c17.7 0 32-14.3 32-32l0-96c0-35.3-28.7-64-64-64L64 192c-35.3 0-64 28.7-64 64l0 96c0 17.7 14.3 32 32 32l32 0 0 64c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-64zM432 248a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/></svg>
Drucken Drucken
</button> </button>
@@ -22,12 +22,12 @@
Stil ändern Stil ändern
</button> </button>
</div> </div>
<StyleSelector <StyleSelector
v-else v-else
:image_id="image.image_id" :image_id="image.image_id"
@styleSelected="(style, imageId) => $emit('styleSelected', style, imageId)" @styleSelected="(style, imageId) => $emit('styleSelected', style, imageId)"
@back="showStyleSelectorView = false" @back="showStyleSelectorView = false"
@close="$emit('close')" @close="$emit('close')"
/> />
</div> </div>
</div> </div>
@@ -36,8 +36,12 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { usePage } from '@inertiajs/vue3';
import StyleSelector from './StyleSelector.vue'; import StyleSelector from './StyleSelector.vue';
const page = usePage();
const settings = page.props.settings;
const props = defineProps({ const props = defineProps({
position: { position: {
type: Object, type: Object,
@@ -115,4 +119,4 @@ const showStyleSelectorView = ref(false);
.context-menu-button:last-child { .context-menu-button:last-child {
border-bottom: none; border-bottom: none;
} }
</style> </style>

View File

@@ -19,14 +19,22 @@
</div> </div>
</div> </div>
<ImageContextMenu <ImageContextMenu
v-if="currentOverlayComponent === 'contextMenu'" v-if="currentOverlayComponent === 'contextMenu'"
:position="contextMenuPosition" :position="contextMenuPosition"
:image="selectedImage" :image="selectedImage"
@close="currentOverlayComponent = null; selectedImage = null" @close="currentOverlayComponent = null; selectedImage = null"
@print="printImage" @print="printImage"
@changeStyle="showStyleSelector" @changeStyle="showStyleSelector"
@styleSelected="applyStyle" @styleSelected="applyStyle"
/>
<StyleSelector
v-if="currentOverlayComponent === 'styleSelector'"
:image_id="selectedImage.id"
@styleSelected="applyStyle"
@back="goBackToContextMenu"
@close="currentOverlayComponent = null; selectedImage = null"
/> />
<div v-if="errorMessage" class="fixed bottom-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg z-50"> <div v-if="errorMessage" class="fixed bottom-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg z-50">
@@ -87,7 +95,6 @@ const currentOverlayComponent = ref(null); // null, 'contextMenu', 'styleSelecto
const contextMenuPosition = ref({ x: 0, y: 0 }); const contextMenuPosition = ref({ x: 0, y: 0 });
const selectedImage = ref(null); const selectedImage = ref(null);
const styledImage = ref(null); // To store the newly styled image const styledImage = ref(null); // To store the newly styled image
const processingImageUuid = ref(null); // To store the UUID of the image being processed
const processingProgress = ref(0); // To store the progress percentage const processingProgress = ref(0); // To store the progress percentage
const errorMessage = ref(null); // New ref for error messages const errorMessage = ref(null); // New ref for error messages
const isLoading = ref(false); // New ref for loading state const isLoading = ref(false); // New ref for loading state
@@ -168,91 +175,124 @@ const applyStyle = (style, imageId) => {
console.log('Applying style:', style.title, 'to image:', imageId); console.log('Applying style:', style.title, 'to image:', imageId);
currentOverlayComponent.value = null; // Close style selector immediately currentOverlayComponent.value = null; // Close style selector immediately
isLoading.value = true; // Show loading spinner isLoading.value = true; // Show loading spinner
processingImageUuid.value = selectedImage.value.uuid; // Set the UUID of the image being processed
processingProgress.value = 0; // Reset progress processingProgress.value = 0; // Reset progress
axios.get('/api/comfyui-url') // Send style change request to backend first
.then(response => { axios.post('/api/images/style-change', {
const comfyUiBaseUrl = response.data.comfyui_url; image_id: imageId,
const wsUrl = `ws://${new URL(comfyUiBaseUrl).host}/ws`; style_id: style.id,
const ws = new WebSocket(wsUrl); })
.then(response => {
console.log('Style change request sent:', response.data);
// Store the prompt_id and plugin from the backend response
const promptId = response.data.prompt_id;
const plugin = response.data.plugin;
ws.onopen = () => { // Handle different plugins differently
console.log('WebSocket connected to ComfyUI.'); if (plugin === 'ComfyUi') {
// Send prompt to ComfyUI via HTTP, then listen for progress via WS // For ComfyUI, use WebSocket for progress monitoring
axios.post('/api/images/style-change', { axios.get(`/api/comfyui-url?style_id=${style.id}`)
image_id: imageId, .then(comfyResponse => {
style_id: style.id, const comfyUiBaseUrl = comfyResponse.data.comfyui_url;
}) const wsUrl = `ws://${new URL(comfyUiBaseUrl).host}/ws`;
.then(response => { const ws = new WebSocket(wsUrl);
console.log('Style change request sent:', response.data);
// Store the prompt_id from the backend response
const promptId = response.data.prompt_id;
ws.onmessage = (event) => { ws.onopen = () => {
const message = JSON.parse(event.data); console.log('WebSocket connected to ComfyUI.');
if (message.type === 'progress') {
console.log('ComfyUI Progress Message:', message);
const { value, max } = message.data;
const progress = (max > 0) ? (value / max) * 100 : 0;
if (message.data.prompt_id === promptId) {
processingProgress.value = progress;
if (processingProgress.value >= 100) { ws.onmessage = (event) => {
console.log('Frontend: Progress reached 100%. Attempting to fetch final image.', { promptId: promptId }); const message = JSON.parse(event.data);
// Fetch the final styled image from the backend if (message.type === 'progress') {
axios.get(`/api/images/fetch-styled/${promptId}`) console.log('ComfyUI Progress Message:', message);
.then(imageResponse => { const { value, max } = message.data;
console.log('Frontend: Successfully fetched styled image.', imageResponse.data); const progress = (max > 0) ? (value / max) * 100 : 0;
styledImage.value = imageResponse.data.styled_image; if (message.data.prompt_id === promptId) {
currentOverlayComponent.value = 'styledImageDisplay'; processingProgress.value = progress;
fetchImages(); // Refresh gallery
}) if (processingProgress.value >= 100) {
.catch(imageError => { console.log('Frontend: Progress reached 100%. Attempting to fetch final image.', { promptId: promptId });
console.error('Frontend: Error fetching styled image:', imageError.response?.data?.error || imageError.message); // Fetch the final styled image from the backend
showError(imageError.response?.data?.error || 'Failed to fetch styled image.'); axios.get(`/api/images/fetch-styled/${promptId}`)
}) .then(imageResponse => {
.finally(() => { console.log('Frontend: Successfully fetched styled image.', imageResponse.data);
console.log('Frontend: Final fetch process completed.'); styledImage.value = imageResponse.data.styled_image;
isLoading.value = false; currentOverlayComponent.value = 'styledImageDisplay';
processingImageUuid.value = null; fetchImages(); // Refresh gallery
processingProgress.value = 0; })
ws.close(); .catch(imageError => {
}); console.error('Frontend: Error fetching styled image:', imageError.response?.data?.error || imageError.message);
} showError(imageError.response?.data?.error || 'Failed to fetch styled image.');
} })
} else { .finally(() => {
console.warn('Received unexpected WebSocket message type:', message.type, message); console.log('Frontend: Final fetch process completed.');
} isLoading.value = false;
}; processingProgress.value = 0;
ws.close();
});
}
}
} else {
console.warn('Received unexpected WebSocket message type:', message.type, message);
}
};
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
showError('WebSocket connection error.');
isLoading.value = false;
processingProgress.value = 0;
};
ws.onclose = () => {
console.log('WebSocket closed.');
};
}) })
.catch(error => { .catch(error => {
console.error('Error applying style:', error); console.error('Error fetching ComfyUI URL:', error);
showError(error.response?.data?.error || 'Failed to apply style.'); showError(error.response?.data?.error || 'Failed to get ComfyUI URL.');
isLoading.value = false; isLoading.value = false;
processingImageUuid.value = null;
processingProgress.value = 0;
ws.close();
}); });
} else {
// For other plugins, use polling approach
const pollForStyledImage = () => {
axios.get(`/api/images/fetch-styled/${promptId}`)
.then(imageResponse => {
console.log('Frontend: Successfully fetched styled image.', imageResponse.data);
styledImage.value = imageResponse.data.styled_image;
currentOverlayComponent.value = 'styledImageDisplay';
fetchImages(); // Refresh gallery
isLoading.value = false;
processingProgress.value = 0;
})
.catch(imageError => {
console.error('Frontend: Error fetching styled image:', imageError.response?.data?.error || imageError.message);
// If the image is not ready yet, continue polling
if (imageError.response?.status === 404) {
// Update progress if available
if (imageError.response?.data?.progress !== undefined) {
processingProgress.value = imageError.response.data.progress;
}
// Continue polling
setTimeout(pollForStyledImage, 2000); // Poll every 2 seconds
} else {
showError(imageError.response?.data?.error || 'Failed to fetch styled image.');
isLoading.value = false;
processingProgress.value = 0;
}
});
}; };
ws.onerror = (error) => { // Start polling for the styled image
console.error('WebSocket error:', error); pollForStyledImage();
showError('WebSocket connection error.'); }
isLoading.value = false; })
processingImageUuid.value = null; .catch(error => {
processingProgress.value = 0; console.error('Error applying style:', error);
}; showError(error.response?.data?.error || 'Failed to apply style.');
isLoading.value = false;
ws.onclose = () => { processingProgress.value = 0;
console.log('WebSocket closed.'); });
};
})
.catch(error => {
console.error('Error fetching ComfyUI URL:', error);
showError(error.response?.data?.error || 'Failed to get ComfyUI URL.');
isLoading.value = false;
});
}; };
const keepStyledImage = (imageToKeep) => { const keepStyledImage = (imageToKeep) => {

View File

@@ -103,6 +103,11 @@ return [
'image_refresh_interval' => 'Bildaktualisierungsintervall (Sekunden)', 'image_refresh_interval' => 'Bildaktualisierungsintervall (Sekunden)',
'new_image_timespan_minutes' => 'Neue Bilder Zeitspanne (Minuten)', 'new_image_timespan_minutes' => 'Neue Bilder Zeitspanne (Minuten)',
'gallery_heading' => 'Galerie Überschrift', 'gallery_heading' => 'Galerie Überschrift',
'max_number_of_copies' => 'Maximale Anzahl an Kopien',
'show_print_button' => 'Button \'Drucken\' beim Bild anzeigen',
'printer' => 'Drucker',
'custom_printer' => 'Eigener Drucker/Adresse',
'custom_printer_address' => 'Druckername oder -adresse',
], ],
], ],
'plugin' => [ 'plugin' => [
@@ -155,6 +160,6 @@ return [
'delete_button' => 'Löschen', 'delete_button' => 'Löschen',
], ],
'loading_spinner' => [ 'loading_spinner' => [
'processing_image' => 'Bild wird verarbeitet...', 'processing_image' => 'Bild wird verarbeitet...',
], ],
]; ];

View File

@@ -102,6 +102,8 @@ return [
'image_refresh_interval' => 'Image Refresh Interval (seconds)', 'image_refresh_interval' => 'Image Refresh Interval (seconds)',
'new_image_timespan_minutes' => 'New Image Timespan (minutes)', 'new_image_timespan_minutes' => 'New Image Timespan (minutes)',
'gallery_heading' => 'Gallery Heading', 'gallery_heading' => 'Gallery Heading',
"max_number_of_copies" => "Max Number of Copies",
"show_print_button" => "Show 'Print' button on image",
], ],
], ],
'user' => [ 'user' => [

View File

@@ -0,0 +1,9 @@
<x-filament-panels::page>
<form wire:submit="save">
{{ $this->form }}
<x-filament-panels::form.actions
:actions="$this->getFormActions()"
/>
</form>
</x-filament-panels::page>

View File

@@ -22,37 +22,37 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach ($this->getTableRecords() as $record) @foreach ($plugins as $plugin)
<x-filament-tables::row> <x-filament-tables::row>
<x-filament-tables::cell> <x-filament-tables::cell>
{{ $record->name }} {{ $plugin->name }}
</x-filament-tables::cell> </x-filament-tables::cell>
<x-filament-tables::cell> <x-filament-tables::cell>
{{ $record->identifier }} {{ $plugin->identifier }}
</x-filament-tables::cell> </x-filament-tables::cell>
<x-filament-tables::cell> <x-filament-tables::cell>
@if ($record->enabled) @if ($plugin->enabled)
<x-filament::icon <x-filament::icon
icon="heroicon-o-check-circle" icon="heroicon-o-check-circle"
class="text-success-500" class="text-success-500 w-5 h-5"
/> />
@else @else
<x-filament::icon <x-filament::icon
icon="heroicon-o-x-circle" icon="heroicon-o-x-circle"
class="text-danger-500" class="text-danger-500 w-5 h-5"
/> />
@endif @endif
</x-filament-tables::cell> </x-filament-tables::cell>
<x-filament-tables::cell> <x-filament-tables::cell>
@if ($record->configured) @if ($plugin->configured)
<x-filament::icon <x-filament::icon
icon="heroicon-o-check-circle" icon="heroicon-o-check-circle"
class="text-success-500" class="text-success-500 w-5 h-5"
/> />
@else @else
<x-filament::icon <x-filament::icon
icon="heroicon-o-x-circle" icon="heroicon-o-x-circle"
class="text-danger-500" class="text-danger-500 w-5 h-5"
/> />
@endif @endif
</x-filament-tables::cell> </x-filament-tables::cell>