Files
imdd_silas/Classes/GifWriter.m
2025-12-15 15:41:02 +01:00

147 lines
5.3 KiB
Matlab

classdef GifWriter < handle
%GIFWRITER Simple class to create GIFs from figures (parallel-safe)
%
% Example:
% g = GifWriter('Name','mySim','Parallel',true);
% parfor i = 1:10
% plot(rand(10,1));
% g.addFrame(1,i);
% end
% g.compile(1);
properties
Name (1,:) char = 'default' % GIF base name
DelayTime (1,1) double = 0.1 % Frame delay in seconds
Parallel (1,1) logical = false % Enable parallel-safe mode
BaseDir (1,:) char % Base directory for temp frames
OutputDir (1,:) char % Final GIF output directory
end
methods
%% Constructor
function obj = GifWriter(varargin)
% Parse name/value pairs
p = inputParser;
addParameter(p, 'Name', 'default', @ischar);
addParameter(p, 'DelayTime', 0.1, @isnumeric);
addParameter(p, 'Parallel', false, @islogical);
addParameter(p, 'OutputDir', fullfile(pwd, 'gif_output'), @ischar);
parse(p, varargin{:});
obj.Name = p.Results.Name;
obj.DelayTime = p.Results.DelayTime;
obj.Parallel = p.Results.Parallel;
obj.OutputDir = p.Results.OutputDir;
obj.BaseDir = fullfile(obj.OutputDir, 'tmp', obj.Name);
if ~exist(obj.BaseDir, 'dir'), mkdir(obj.BaseDir); end
if ~exist(obj.OutputDir, 'dir'), mkdir(obj.OutputDir); end
end
%%
function addFrame(obj, figInput, pos)
%ADDFRAME Add a figure frame to the GIF (supports parallel mode)
%
% Usage:
% obj.addFrame(figHandle)
% obj.addFrame(figNum)
% obj.addFrame(figHandle, pos) % parallel mode
% obj.addFrame(figNum, pos)
%
% In parallel mode, 'pos' must be a unique integer (loop index).
if nargin < 3, pos = []; end
% --- Resolve figure handle ---
if isnumeric(figInput)
% User passed a figure number
if ~ishandle(figInput)
warning('GifWriter:addFrame', 'Figure %d not found.', figInput);
return;
end
figHandle = figure(figInput);
elseif isa(figInput, 'matlab.ui.Figure')
figHandle = figInput;
else
error('GifWriter:addFrame:InvalidInput', ...
'Input must be a figure handle or figure number.');
end
% --- Parallel-safe frame writing ---
if obj.Parallel
if isempty(pos)
error('GifWriter:ParallelMode', ...
'In parallel mode, provide a unique ''pos'' identifier.');
end
% Directory for this figure number
frameDir = fullfile(obj.BaseDir, sprintf('fig_%d', figHandle.Number));
if ~exist(frameDir, 'dir')
mkdir(frameDir);
end
% File path for this frame
frameFile = fullfile(frameDir, sprintf('frame_%05d.png', pos));
% Export to PNG (headless-safe)
exportgraphics(figHandle, frameFile, 'Resolution', 150);
else
% --- Serial mode: append directly to GIF ---
gifFile = fullfile(obj.OutputDir, ...
sprintf('%s_fig_%d.gif', obj.Name, figHandle.Number));
% Export frame temporarily
tmpFile = [tempname, '.png'];
exportgraphics(figHandle, tmpFile, 'Resolution', 150);
img = imread(tmpFile);
delete(tmpFile);
% Append to GIF
[A, map] = rgb2ind(img, 256);
if ~isfile(gifFile)
imwrite(A, map, gifFile, 'gif', ...
'LoopCount', Inf, 'DelayTime', obj.DelayTime);
else
imwrite(A, map, gifFile, 'gif', ...
'WriteMode', 'append', 'DelayTime', obj.DelayTime);
end
end
end
%% Compile all PNGs into a GIF (and clean up)
function compile(obj, fignum)
figDir = fullfile(obj.BaseDir, sprintf('fig_%d', fignum));
gifFile = fullfile(obj.OutputDir, sprintf('%s_fig_%d.gif', obj.Name, fignum));
frames = dir(fullfile(figDir, 'frame_*.png'));
if isempty(frames)
warning('GifWriter:NoFrames', 'No frames found for figure %d.', fignum);
return;
end
% Sort by frame name
[~, idx] = sort({frames.name});
frames = frames(idx);
% Combine into a GIF
for i = 1:numel(frames)
img = imread(fullfile(frames(i).folder, frames(i).name));
[A, map] = rgb2ind(img, 256);
if i == 1
imwrite(A, map, gifFile, 'gif', ...
'LoopCount', Inf, 'DelayTime', obj.DelayTime);
else
imwrite(A, map, gifFile, 'gif', ...
'WriteMode', 'append', 'DelayTime', obj.DelayTime);
end
end
% Clean up temporary frames
rmdir(figDir, 's');
end
end
end