from Export image to TIF or TIFF file of selected

本文介绍了一个MATLAB函数imwrite2tif,用于将图像数据及其元数据导出为TIFF文件格式。通过指定数据类型、文件名及额外的TIFF标签值,用户可以灵活地导出不同格式的图像。示例代码演示了如何使用此函数进行基本操作。
imwrite2tif(varargin)
function imwrite2tif(varargin)
% IMWRITE2TIF Write image to tif file with specified datatype.
%   IMWRITE2TIF(IMGDATA,HEADER,IMFILE,DATATYPE) exports IMGDATA with HEADER
%   to TIF file named IMFILE. HEADER is usally obtained by IMFINFO from 
%   original image file, and it can also be left empty. String DATATYPE 
%   specifies data type for the export. Supported data types include 
%   logical, uint8, int8, uint16, int16, uint32, int32, uint64, int64, 
%   single and double.
%
%   IMWRITE2TIF(IMGDATA,HEADER,IMFILE,DATATYPE,TAG NAME1,TAG VALUE1,TAG NAME2,
%   TAG VALUE2,...) writes with specified Matlab supported TIF tag values.
%   These new tag values overide those already defined in HEADER.
%
%   Note 1: 
%       to avoid errors such as '??? Error using ==> tifflib The value for
%       MaxSampleValue must be ...', overide tag MaxSampleValue by Matlab
%       supported values. Or simply remove the tag from HEADER.
%
%   Note 2:
%       Overwriting of the existing image files is not checked. Be cautious
%       with the export image file name.
%
%   Example 1:
%       imgdata = imread('ngc6543a.jpg');
%       header  = imfinfo('ngc6543a.jpg');
%       imwrite2tif(imgdata,header,'new_ngc6543a.tif','uint8');
%
%   Example 2:
%       imgdata = imread('mri.tif');
%       imwrite2tif(imgdata,[],'new_mri.tif','int32','Copyright','MRI',
%       'Compression',1);
%
%   More information can be found by searching for 'Exporting Image Data 
%   and Metadata to TIFF Files' in Matlab Help.

%   Zhang Jiang 
%   $Revision: 1.0 $  $Date: 2011/02/23 $

if nargin<4 || mod(nargin,2)==1
    error('Invalid number of input arguments.');
end

% assign input argument
imgdata  = varargin{1};
header   = varargin{2};
imfile   = varargin{3};
datatype = varargin{4};
if nargin>4
    tag_name  = cell((nargin-4)/2,1);
    tag_value = cell((nargin-4)/2,1);
    for ii=1:(nargin-4)/2
        tag_name{ii}  = varargin{2*ii+3};
        tag_value{ii} = varargin{2*ii+4};
    end
end

% check errors
if ~isnumeric(imgdata)
     error('The first input argument (image data) must be a numeric array.');
end
if ~isempty(header) && ~isstruct(header)
    error('The second input argument (header info) must be empty or a structure.');
end
if ~ischar(imfile)
    error('The third input argument (output tif file name) must be string.');
end
switch lower(datatype)
    case 'logical'
        BitsPerSample = 1;
        SampleFormat  = 1;
        imgdata = logical(imgdata);
    case 'uint8'
        BitsPerSample = 8;   
        SampleFormat  = 1;         
        imgdata = uint8(imgdata);    
    case 'int8'
        BitsPerSample = 8;
        SampleFormat  = 2;       
        imgdata = int8(imgdata);          
    case 'uint16'
        BitsPerSample = 16;       
        SampleFormat  = 1;         
        imgdata = uint16(imgdata);           
    case 'int16'
        BitsPerSample = 16;       
        SampleFormat  = 2;         
        imgdata = int16(imgdata);        
    case 'uint32'
        BitsPerSample = 32;       
        SampleFormat  = 1;                
        imgdata = uint32(imgdata);            
    case 'int32'
        BitsPerSample = 32;       
        SampleFormat  = 2;     
        imgdata = int32(imgdata);         
     case 'single'
        BitsPerSample = 32;        
        SampleFormat  = 3;                
        imgdata = single(imgdata);       
    case {'uint64','int64','double'}
        BitsPerSample = 64;        
        SampleFormat  = 3;                
        imgdata = double(imgdata);           
    otherwise
        error('Invalid output data type.');
end

% creat a Tiff object
t = Tiff(imfile,'w');

% duplicate tags from header
tagnamelist = Tiff.getTagNames();
tagnamelist_delete = {...
    'StripByteCounts';...
    'StripOffsets';
    'TileByteCounts';...
    'TileOffsets';...
    'MaxSampleValue';...
    'MinSampleValue';...
    'ResolutionUnit'};
for ii=1:length(tagnamelist_delete)    % remove read only tag names
    tagnamelist(strcmpi(tagnamelist_delete{ii},tagnamelist)) = [];
end
if ~isempty(header)
    for ii=1:length(tagnamelist)
        if isfield(header,tagnamelist{ii}) && ~isempty(header.(tagnamelist{ii}))
           tagstruct.(tagnamelist{ii}) = header.(tagnamelist{ii});
        end
    end
end

% update tags determined from imgdata and datatype
tagstruct.ImageLength = size(imgdata,1);
tagstruct.ImageWidth = size(imgdata,2);
tagstruct.SamplesPerPixel = size(imgdata,3);
tagstruct.SampleFormat = SampleFormat;
tagstruct.BitsPerSample = BitsPerSample;

% update some default tags (these tags can be overriden by user input below)
tagstruct.Photometric = Tiff.Photometric.MinIsBlack;
tagstruct.Compression = 1;
tagstruct.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky;
tagstruct.Software = 'MATLAB';

% update user input tag values
if nargin>4
    for ii=1:length(tag_name)
        tagstruct.(tag_name{ii}) = tag_value{ii};
    end
end

% set tags and write to tif file
t.setTag(tagstruct)
t.write(imgdata);
t.close();   
2个脚本整合。一个批量渲染序列帧工具、一个批量生成GIF工具。 批量渲染完成后 去掉渲染完成提示 去掉打开文件夹 直接自动批量生成GIF,GIF输出目录 默认为 批量渲染序列帧文件夹 的 父目录。接下来我会给两个脚本。 脚本1:try(destroyDialog renderSeqRollout)catch() rollout renderSeqRollout "批量渲染序列帧工具 V5.4" width:423 height:500 ( -- 单场景渲染控件 groupBox grpSingle "单场景渲染设置" width:390 height:70 pos:[15, 10] checkbox chk_useActiveRange "使用时间滑块范围" checked:true align:#left offset:[10,-50] across:2 checkbox chk_openFolder "渲染后打开文件夹" checked:true align:#right offset:[-120,-50] spinner spn_startFrame "起始帧:" range:[-99999,99999,0] type:#integer fieldWidth:50 offset:[10,0] across:2 spinner spn_endFrame "结束帧:" range:[-99999,99999,100] type:#integer fieldWidth:50 offset:[-90,0] -- 渲染尺寸设置 groupBox grpRenderSize "渲染尺寸设置" width:390 height:90 pos:[15, 85] checkbox chk_customSize "使用自定义尺寸" checked:false align:#left offset:[10,-30] checkbox chk_lockAspect "锁定宽高比" checked:false align:#right offset:[-120,-30] across:2 spinner spn_width "宽度:" range:[1,10000,renderWidth] type:#integer fieldWidth:50 offset:[10,0] across:2 spinner spn_height "高度:" range:[1,10000,renderHeight] type:#integer fieldWidth:50 offset:[-90,0] -- 批量渲染控件 groupBox grpBatch "批量渲染设置" width:390 height:200 pos:[15, 180] checkbox chk_batchMode "批量渲染模式" checked:false align:#left offset:[10,-185] label lbl_batchPath "当前文件夹路径:" align:#left offset:[10,10] across:2 editText edt_batchPath "" width:230 height:20 align:#right offset:[-150,8] readOnly:true button btn_refresh "刷新" width:50 height:20 align:#right offset:[-90,-25] listBox lst_files "MAX文件列表" height:6 width:370 pos:[20, 265] -- 通用控件 button btn_render "渲染序列帧" width:390 height:40 pos:[15, 405] progressBar pb_progress "进度条:" color:(color 0 128 255) height:15 pos:[10, 455] label lbl_status "状态: 准备就绪" align:#left offset:[0,5] pos:[15, 475] -- 局部变量 local frameCount = 0 local renderedFrames = 0 local outputDir = "" local outputPattern = "" local sceneName = "" local batchFiles = #() local currentBatchIndex = 1 local batchTotalFiles = 0 local originalScenePath = "" local originalSceneFile = "" local aspectRatio = 1.0 -- 用于存储宽高比 -- 扫描当前文件夹中的MAX文件 fn scanMaxFiles = ( local currentPath = maxFilePath if currentPath != "" then ( edt_batchPath.text = currentPath batchFiles = getFiles (currentPath + "\\*.max") lst_files.items = for f in batchFiles collect filenameFromPath f lbl_status.text = "状态: 找到 " + (batchFiles.count as string) + " 个MAX文件" ) else ( lbl_status.text = "状态: 场景未保存,请先保存场景" edt_batchPath.text = "" lst_files.items = #() ) ) -- 初始化时设置正确的帧范围和渲染尺寸 on renderSeqRollout open do ( spn_startFrame.value = animationRange.start.frame as integer spn_endFrame.value = animationRange.end.frame as integer spn_width.value = renderWidth spn_height.value = renderHeight -- 计算初始宽高比 if renderHeight > 0 do aspectRatio = renderWidth as float / renderHeight as float -- 设置控件状态 spn_startFrame.enabled = not chk_useActiveRange.checked spn_endFrame.enabled = not chk_useActiveRange.checked spn_width.enabled = chk_customSize.checked spn_height.enabled = chk_customSize.checked scanMaxFiles() -- 自动扫描当前文件夹 ) on chk_useActiveRange changed state do ( spn_startFrame.enabled = not state spn_endFrame.enabled = not state if state do ( spn_startFrame.value = animationRange.start.frame as integer spn_endFrame.value = animationRange.end.frame as integer ) ) -- 自定义尺寸复选框事件 on chk_customSize changed state do ( spn_width.enabled = state spn_height.enabled = state chk_lockAspect.enabled = state ) -- 更新宽高比锁定 fn updateAspectLock = ( if chk_lockAspect.checked and spn_height.value > 0 do ( aspectRatio = spn_width.value as float / spn_height.value as float ) ) -- 宽度变化事件 on spn_width changed val do ( if chk_lockAspect.checked and aspectRatio > 0 and chk_customSize.checked then ( local newHeight = (val / aspectRatio) as integer if newHeight > 0 do ( spn_height.value = newHeight ) ) else ( updateAspectLock() ) ) -- 高度变化事件 on spn_height changed val do ( if chk_lockAspect.checked and aspectRatio > 0 and chk_customSize.checked then ( local newWidth = (val * aspectRatio) as integer if newWidth > 0 do ( spn_width.value = newWidth ) ) else ( updateAspectLock() ) ) -- 锁定宽高比复选框事件 on chk_lockAspect changed state do ( if state and spn_height.value > 0 do ( aspectRatio = spn_width.value as float / spn_height.value as float ) ) -- 获取时间滑块的实际范围 fn getActualTimeRange = ( local actualStart = animationRange.start.frame as integer local actualEnd = animationRange.end.frame as integer return #(actualStart, actualEnd) ) -- 刷新文件列表按钮事件 on btn_refresh pressed do ( scanMaxFiles() ) -- 渲染单帧函数 fn renderSingleFrame frameIndex = ( -- 设置当前帧 sliderTime = frameIndex -- 生成当前帧的文件名 frameNumber = formattedPrint frameIndex format:"06d" frameFile = substituteString outputPattern "######" frameNumber -- 执行渲染(禁用所有窗口) render outputfile:frameFile vfb:false progressbar:false quiet:true -- 更新进度 renderedFrames += 1 pb_progress.value = (100.0 * renderedFrames / frameCount) lbl_status.text = "状态: 渲染中 (" + renderedFrames as string + "/" + frameCount as string + ")" ) -- 渲染当前场景 fn renderCurrentScene = ( -- 检查场景是否保存 if maxFileName == "" then ( lbl_status.text = "状态: 错误 - 场景未保存!" messageBox "请先保存场景文件!" title:"错误" return false ) -- 获取场景信息 sceneName = getFilenameFile maxFileName local scenePath = maxFilePath -- 创建输出目录 outputDir = scenePath + sceneName if not doesFileExist outputDir then ( makeDir outputDir lbl_status.text = "状态: 创建目录 - " + outputDir ) -- 获取实际时间范围 local actualRange = getActualTimeRange() -- 设置渲染范围 local startFrame, endFrame if chk_useActiveRange.checked then ( startFrame = actualRange[1] endFrame = actualRange[2] ) else ( startFrame = spn_startFrame.value endFrame = spn_endFrame.value ) -- 计算帧数 frameCount = endFrame - startFrame + 1 if frameCount <= 0 then ( lbl_status.text = "状态: 错误 - 无效帧范围!" messageBox "帧范围无效!起始帧不能大于结束帧。" title:"错误" return false ) -- 创建输出文件名模式(JPG格式) outputPattern = outputDir + "\\" + sceneName + "_" + "######.jpg" -- 保存当前渲染器设置 local oldRenderer = renderers.current -- 将渲染器设置为Scanline Renderer renderers.current = Default_Scanline_Renderer() -- 检查渲染器 if renderers.current == undefined then ( lbl_status.text = "状态: 错误 - 没有激活的渲染器!" messageBox "没有激活的渲染器!请选择渲染器。" title:"错误" return false ) -- 保存当前渲染设置 local oldOutput = rendOutputFilename local oldSaveFile = rendSaveFile local oldTimeType = rendTimeType local oldNThFrame = rendNThFrame local oldStart = rendStart local oldEnd = rendEnd -- 保存当前渲染尺寸 local oldWidth = renderWidth local oldHeight = renderHeight -- 如果启用自定义尺寸,设置新的渲染尺寸 if chk_customSize.checked then ( renderWidth = spn_width.value renderHeight = spn_height.value ) -- 设置渲染参数 rendOutputFilename = outputPattern rendSaveFile = true rendTimeType = 3 -- 渲染范围 rendNThFrame = 1 -- 每帧渲染 rendStart = startFrame rendEnd = endFrame -- 提交渲染设置 renderSceneDialog.update() renderSceneDialog.commit() -- 逐帧渲染 renderedFrames = 0 local success = true for f = startFrame to endFrame do ( try ( renderSingleFrame f ) catch ( lbl_status.text = "状态: 错误 - 渲染帧 " + f as string + " 时失败" messageBox ("渲染帧 " + f as string + " 时失败:\n" + getCurrentException()) title:"渲染错误" success = false exit ) ) -- 恢复原始渲染设置 rendOutputFilename = oldOutput rendSaveFile = oldSaveFile rendTimeType = oldTimeType rendNThFrame = oldNThFrame rendStart = oldStart rendEnd = oldEnd renderers.current = oldRenderer -- 恢复原始渲染尺寸 renderWidth = oldWidth renderHeight = oldHeight renderSceneDialog.commit() return success ) -- 渲染按钮事件 on btn_render pressed do ( -- 重置状态 pb_progress.value = 0 renderedFrames = 0 setWaitCursor() if chk_batchMode.checked then ( -- 批量渲染模式 if batchFiles.count == 0 then ( lbl_status.text = "状态: 错误 - 未找到MAX文件!" messageBox "当前文件夹中没有找到MAX文件!" title:"错误" return false ) -- 保存当前场景信息 originalScenePath = maxFilePath originalSceneFile = maxFileName batchTotalFiles = batchFiles.count currentBatchIndex = 1 lbl_status.text = "状态: 开始批量渲染 (" + batchTotalFiles as string + " 个文件)..." for i = 1 to batchFiles.count do ( local currentFile = batchFiles[i] currentBatchIndex = i -- 更新文件进度 pb_progress.value = (100.0 * (i-1) / batchTotalFiles) lbl_status.text = "状态: 正在渲染文件 " + i as string + "/" + batchTotalFiles as string + ": " + filenameFromPath currentFile -- 加载MAX文件 loadMaxFile currentFile quiet:true -- 渲染当前场景 local success = renderCurrentScene() if not success then ( messageBox ("渲染失败: " + currentFile) title:"错误" exit ) ) -- 恢复原始场景 if originalSceneFile != "" then ( local originalFilePath = originalScenePath + originalSceneFile if doesFileExist originalFilePath then ( loadMaxFile originalFilePath quiet:true lbl_status.text = "状态: 已恢复原始场景" ) ) pb_progress.value = 100 lbl_status.text = "状态: 批量渲染完成! (" + batchTotalFiles as string + " 个文件)" messageBox ("批量渲染成功完成!\n共渲染了 " + batchTotalFiles as string + " 个场景") title:"渲染成功" ) else ( -- 单场景渲染模式 lbl_status.text = "状态: 开始渲染当前场景..." local success = renderCurrentScene() if success then ( pb_progress.value = 100 lbl_status.text = "状态: 渲染完成! (" + frameCount as string + " 帧)" -- 可选:打开输出目录 if chk_openFolder.checked and doesFileExist outputDir then ( shellLaunch "explorer.exe" outputDir ) messageBox ("渲染成功完成!\n共渲染了 " + frameCount as string + " 帧\n输出目录: " + outputDir) title:"渲染成功" ) else ( pb_progress.value = 0 ) ) setArrowCursor() ) ) createdialog renderSeqRollout 脚本2:rollout gifMakerRollout "GIF生成工具 - 多文件夹处理" width:400 height:420 ( -- UI控件声明 groupBox grpSettings "设置" pos:[10,10] width:380 height:200 editText edtFFmpegPath "FFmpeg路径:" pos:[20,35] fieldWidth:300 labelOnTop:true text:"" -- 初始为空 button btnBrowseFFmpeg "浏览..." pos:[330,35] width:40 height:20 editText edtParentFolder "父文件夹路径:" pos:[20,75] fieldWidth:300 labelOnTop:true text:(getDir #export + "\\") -- 默认导出目录 button btnBrowseParentFolder "浏览..." pos:[330,75] width:40 height:20 label lblPatternHint "图片序列格式:" pos:[20,115] width:100 height:20 dropdownlist ddlPattern "序列格式" pos:[20,135] width:300 height:40 items:#("自动检测序列帧", "手动输入格式...") editText edtCustomPattern "" pos:[20,175] fieldWidth:300 height:20 visible:false button btnTestPattern "测试格式" pos:[330,175] width:40 height:20 visible:false groupBox grpAnimation "动画设置" pos:[10,220] width:380 height:80 spinner spnFrameRate "帧率 (fps):" pos:[20,240] range:[1,60,30] type:#integer fieldWidth:50 spinner spnSpeedFactor "速度因子:" pos:[150,240] range:[0.1,10.0,1.0] type:#float scale:0.1 fieldWidth:50 label lblSpeedHint "1.0=正常 | 2.0=2倍速 | 0.5=半速" pos:[210,240] width:150 height:20 checkbox chkAutoOpen "生成后打开文件夹" pos:[20,280] checked:true progressBar pbProgress "" pos:[10,310] width:380 height:20 value:0 color:(color 0 255 0) label lblStatus "" pos:[10,335] width:380 height:20 button btnGenerate "批量生成GIF" pos:[100,360] width:100 height:30 button btnCancel "取消" pos:[220,360] width:100 height:30 -- 自动查找脚本目录下的FFmpeg fn findFFmpegInScriptDir = ( -- 获取脚本文件路径 local scriptPath = getSourceFileName() if scriptPath == undefined do return "" -- 获取脚本所在目录 local scriptDir = getFilenamePath scriptPath -- 检查常见位置 local possiblePaths = #( scriptDir + "ffmpeg.exe", -- 直接在脚本目录 scriptDir + "ffmpeg\\bin\\ffmpeg.exe", -- 在ffmpeg子目录 scriptDir + "bin\\ffmpeg.exe", -- 在bin子目录 scriptDir + "tools\\ffmpeg.exe" -- 在tools子目录 ) -- 查找存在的路径 for path in possiblePaths do ( if doesFileExist path do return path ) return "" ) -- 辅助函数:验证路径是否存在 fn validatePath path isFile:false = ( if path == "" do ( messageBox "路径不能为空!" title:"错误" return false ) if isFile then ( if not doesFileExist path then ( messageBox ("路径不存在: \n" + path) title:"错误" return false ) ) else ( if not doesDirectoryExist path then ( messageBox ("文件夹不存在: \n" + path) title:"错误" return false ) ) return true ) -- 获取图片分辨率 - 更可靠的方法 fn getImageResolution imgPath = ( if doesFileExist imgPath then ( try ( -- 使用openBitmap方法获取分辨率 local bmp = openBitmap imgPath if bmp != undefined then ( local w = bmp.width local h = bmp.height close bmp return [w, h] ) ) catch ( format "无法读取图片分辨率: %\n" imgPath ) ) return [0,0] ) -- 检测序列帧模式并获取分辨率 fn detectSequencePatternAndResolution folderPath = ( local sequenceFiles = #() if not doesDirectoryExist folderPath do return #("", [0,0]) -- 获取所有图片文件 imgFiles = getFiles (folderPath + "*.jpg") join imgFiles (getFiles (folderPath + "*.jpeg")) join imgFiles (getFiles (folderPath + "*.png")) join imgFiles (getFiles (folderPath + "*.bmp")) join imgFiles (getFiles (folderPath + "*.tga")) join imgFiles (getFiles (folderPath + "*.tif")) join imgFiles (getFiles (folderPath + "*.tiff")) if imgFiles.count == 0 do return #("", [0,0]) -- 按文件名排序 sort imgFiles -- 分析文件名模式 patterns = #() resolutions = #() for i = 1 to imgFiles.count do ( fileName = filenameFromPath imgFiles[i] baseName = getFilenameFile fileName fileExt = getFilenameType fileName -- 查找数字序列部分 digits = "" prefix = "" suffix = "" digitCount = 0 digitStart = 0 -- 查找数字序列 for j = baseName.count to 1 by -1 do ( char = baseName[j] if (findString "0123456789" char) != undefined then ( digits = char + digits digitCount += 1 digitStart = j ) else ( if digitCount > 0 do exit ) ) -- 如果找到数字序列 if digitCount > 0 then ( prefix = substring baseName 1 (digitStart - 1) suffix = substring baseName (digitStart + digitCount) -1 -- 生成序列模式 pattern = prefix + "%0" + (digitCount as string) + "d" + suffix + fileExt append patterns pattern -- 获取图片分辨率(只取第一张) if i == 1 then ( resolution = getImageResolution imgFiles[i] append resolutions resolution ) ) ) if patterns.count == 0 do return #("", [0,0]) -- 找出最常见的模式 patternCounts = #() for p in patterns do ( index = findItem patternCounts p if index == 0 then ( append patternCounts p append patternCounts 1 ) else ( patternCounts[index+1] += 1 ) ) -- 选择出现次数最多的模式 maxCount = 0 bestPattern = "" for i = 1 to patternCounts.count by 2 do ( if patternCounts[i+1] > maxCount then ( maxCount = patternCounts[i+1] bestPattern = patternCounts[i] ) ) -- 转换为FFmpeg格式 (使用两个%表示单个%) if bestPattern != "" do ( bestPattern = substituteString bestPattern "%0" "%%0" ) -- 返回最佳模式和分辨率 return #(bestPattern, resolutions[1]) ) -- 测试序列格式 fn testSequencePattern folderPath pattern = ( if not doesDirectoryExist folderPath do return false -- 转换为搜索模式 searchPattern = substituteString pattern "%%0" "*" searchPattern = substituteString searchPattern "d" "*" -- 查找匹配文件 matchedFiles = getFiles (folderPath + searchPattern) return matchedFiles.count > 0 ) -- 浏览FFmpeg路径 on btnBrowseFFmpeg pressed do ( local path = getOpenFileName caption:"选择FFmpeg可执行文件" \ filename:edtFFmpegPath.text \ types:"可执行文件(*.exe)|*.exe" if path != undefined do edtFFmpegPath.text = path ) -- 浏览父文件夹 on btnBrowseParentFolder pressed do ( local path = getSavePath caption:"选择父文件夹" \ initialDir:edtParentFolder.text if path != undefined do ( folderPath = path + "\\" edtParentFolder.text = folderPath ) ) -- 序列格式下拉列表改变事件 on ddlPattern selected sel do ( if sel == 2 then ( edtCustomPattern.visible = true btnTestPattern.visible = true edtCustomPattern.text = "例如: image_%%03d.jpg" ) else ( edtCustomPattern.visible = false btnTestPattern.visible = false ) ) -- 测试序列格式按钮 on btnTestPattern pressed do ( folderPath = edtParentFolder.text if not doesDirectoryExist folderPath do ( messageBox "文件夹不存在!" title:"错误" return false ) -- 获取第一个子文件夹进行测试 subFolders = getDirectories (folderPath + "*") if subFolders.count == 0 do ( messageBox "父文件夹中没有子文件夹!" title:"错误" return false ) testFolder = subFolders[1] pattern = edtCustomPattern.text if testSequencePattern testFolder pattern then ( messageBox ("格式有效!\n在第一个子文件夹中检测到匹配文件。") title:"测试结果" ) else ( messageBox ("未找到匹配文件!\n请检查格式是否正确。") title:"测试结果" ) ) -- 生成GIF (使用setpts滤镜实现真正的速度控制) on btnGenerate pressed do ( -- 获取UI中的值 ffmpegPath = edtFFmpegPath.text parentFolder = edtParentFolder.text frameRate = spnFrameRate.value speedFactor = spnSpeedFactor.value -- 获取序列格式 if ddlPattern.selection == 1 then ( useAutoPattern = true ) else ( useAutoPattern = false manualPattern = edtCustomPattern.text ) -- 验证FFmpeg路径 if ffmpegPath == "" do ( messageBox "请设置FFmpeg路径!" title:"错误" return false ) if not validatePath ffmpegPath isFile:true do return false if not validatePath parentFolder do return false -- 确保文件夹以反斜杠结尾 if not matchPattern parentFolder pattern:"*\\" do parentFolder += "\\" -- 获取所有子文件夹 subFolders = getDirectories (parentFolder + "*") if subFolders.count == 0 do ( messageBox "父文件夹中没有子文件夹!" title:"错误" return false ) -- 初始化进度条 pbProgress.value = 0 pbProgress.color = (color 0 255 0) lblStatus.text = "准备开始处理..." totalFolders = subFolders.count processedFolders = 0 skippedFolders = 0 generatedGifs = #() -- 处理每个子文件夹 for folder in subFolders do ( -- 更新状态 folderName = getFilenameFile (trimRight folder "\\") lblStatus.text = "处理中: " + folderName processedFolders += 1 pbProgress.value = (100.0 * processedFolders / totalFolders) -- 强制更新UI windows.processPostedMessages() -- 检测序列模式并获取分辨率 local patternAndRes if useAutoPattern then ( patternAndRes = detectSequencePatternAndResolution folder imgPattern = patternAndRes[1] resolution = patternAndRes[2] if imgPattern == "" then ( format "跳过文件夹 [%] - 未检测到序列帧\n" folderName skippedFolders += 1 continue ) if resolution.x == 0 or resolution.y == 0 then ( format "跳过文件夹 [%] - 无法获取分辨率\n" folderName skippedFolders += 1 continue ) ) else ( imgPattern = manualPattern -- 尝试获取第一张图片的分辨率 searchPattern = substituteString imgPattern "%%0" "000" searchPattern = substituteString searchPattern "d" "*" imgFiles = getFiles (folder + searchPattern) if imgFiles.count > 0 then ( resolution = getImageResolution imgFiles[1] if resolution.x == 0 or resolution.y == 0 then ( format "跳过文件夹 [%] - 无法获取分辨率\n" folderName skippedFolders += 1 continue ) ) else ( format "跳过文件夹 [%] - 没有匹配的图片文件\n" folderName skippedFolders += 1 continue ) ) -- 构建输入输出路径 fullInputPath = folder + imgPattern fullOutputPath = parentFolder + folderName + ".gif" -- 删除已存在的GIF文件以确保覆盖 if doesFileExist fullOutputPath do ( deleteFile fullOutputPath format "已删除旧文件: %\n" fullOutputPath ) -- 使用setpts滤镜实现真正的速度控制 setptsFilter = "setpts=PTS/" + (speedFactor as string) scaleFilter = "scale=" + (resolution.x as string) + ":" + (resolution.y as string) + ":flags=lanczos" -- 构建完整的ffmpeg命令 ffmpegCmd = "\"" + ffmpegPath + "\" -y -framerate " + (frameRate as string) + \ " -i \"" + fullInputPath + "\" " + \ "-vf \"" + setptsFilter + "," + scaleFilter + "\" " + \ "-r " + (frameRate as string) + " " + \ "-loop 0 \"" + fullOutputPath + "\"" -- 输出命令到监听器以便调试 format "处理文件夹 [%]:\n命令: %\n" folderName ffmpegCmd lblStatus.text = "处理中: " + folderName + " (" + resolution.x as string + "x" + resolution.y as string + ")" -- 创建临时批处理文件 tempBatFile = getDir #temp + "\\make_gif_" + (random 1 10000) as string + ".bat" batContent = "@echo off\n" + ffmpegCmd -- 创建并写入批处理文件 success = false f = createFile tempBatFile if f != undefined then ( format "%" batContent to:f close f success = true ) else ( format "无法创建临时批处理文件!\n" ) -- 执行批处理文件 (隐藏窗口) if success do ( exitCode = HiddenDosCommand ("\"" + tempBatFile + "\"") format "退出代码: %\n" exitCode -- 检查输出文件是否存在 if doesFileExist fullOutputPath then ( append generatedGifs fullOutputPath format "成功生成GIF: %\n" fullOutputPath ) else ( format "生成GIF失败: %\n" fullOutputPath ) -- 清理临时文件 if doesFileExist tempBatFile do deleteFile tempBatFile ) ) -- 完成进度条 pbProgress.value = 100 lblStatus.text = "处理完成! 已生成 " + (generatedGifs.count as string) + " 个GIF" -- 如果需要,打开输出文件夹 if chkAutoOpen.checked and generatedGifs.count > 0 do ( shellLaunch "explorer.exe" ("/e,\"" + parentFolder + "\"") ) messageBox ("成功生成 " + (generatedGifs.count as string) + " 个GIF!\n跳过 " + (skippedFolders as string) + " 个文件夹。") title:"完成" ) -- 取消按钮 on btnCancel pressed do ( DestroyDialog gifMakerRollout ) -- 初始化UI on gifMakerRollout open do ( ddlPattern.items = #("自动检测序列帧", "手动输入格式...") -- 自动查找FFmpeg路径 local autoFFmpegPath = findFFmpegInScriptDir() if autoFFmpegPath != "" then ( edtFFmpegPath.text = autoFFmpegPath format "自动设置FFmpeg路径: %\n" autoFFmpegPath ) else ( messageBox "请设置FFmpeg路径!\n点击'浏览...'按钮选择ffmpeg.exe文件。" title:"提示" ) ) ) -- 创建UI窗口 CreateDialog gifMakerRollout
最新发布
08-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值