VampConvert.dpr 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. {
  2. Vampyre Imaging Library Demo
  3. Vampyre Image Converter (ObjectPascal, low level, Win32/Linux/DOS)
  4. tested in Delphi 7/10, Free Pascal 2.0.4 (Win32/Linux/DOS)
  5. written by Marek Mauder
  6. Image Converter is command line tool for converting images between
  7. file and data formats. It also provides some basic manipulation functions
  8. like resizing, rotating, or color reduction.
  9. See PrintUsage procedure for usage details (or just run binary without parameters).
  10. Note: Operations (change format, resize, rotate) are processed in the same order
  11. as they appear on the command line.
  12. }
  13. program VampConvert;
  14. {$APPTYPE CONSOLE}
  15. {$I ImagingOptions.inc}
  16. uses
  17. SysUtils,
  18. Classes,
  19. ImagingTypes,
  20. Imaging,
  21. ImagingUtility;
  22. const
  23. DefaultOutputFile = 'output.png';
  24. DefaultFileFormat = 'png';
  25. const
  26. STrue = 'true';
  27. SFalse = 'false';
  28. var
  29. InFile, OutFile: string;
  30. Operations: TStringList;
  31. procedure PrintHeader;
  32. begin
  33. WriteLn('Vampyre Image Converter (library version ', Imaging.GetVersionStr, ')');
  34. WriteLn('by Marek Mauder');
  35. WriteLn;
  36. end;
  37. procedure PrintUsage;
  38. var
  39. I: TImageFormat;
  40. Info: TImageFormatInfo;
  41. begin
  42. WriteLn('Usage:');
  43. WriteLn('VampConvert [-op=arg] [..] -infile=file.ext [..] [-outfile=file.ext] [-op=arg]');
  44. WriteLn(' Options:');
  45. WriteLn(' -infile: specify input image file path');
  46. WriteLn(' -outfile: specify output image file path');
  47. WriteLn(' argument: file path or "*.ext" where input file name will be used ');
  48. WriteLn(' but with "ext" extension');
  49. WriteLn(' Operations:');
  50. WriteLn(' Note: they are processed in the same order as they appear on command line');
  51. WriteLn(' -format: changes data format of input images');
  52. WriteLn(' argument: name of data format supported by Imaging like A8R8G8B8');
  53. WriteLn(' -resize: changes size of input images');
  54. WriteLn(' argument: string in format AxBxC (%dx%dx%s) where A is desired');
  55. WriteLn(' width, B is desired height, and C is resampling filter used.');
  56. WriteLn(' If A or B is 0 then original dimension will be preserved.');
  57. WriteLn(' C is optional and can have one of following values: ');
  58. WriteLn(' nearest(default), bilinear, bicubic.');
  59. WriteLn(' -flip: flips input images upside down');
  60. WriteLn(' -mirror: mirrors input images left to right');
  61. WriteLn(' -colorcount: reduces number of colors in image');
  62. WriteLn(' argument: number of desired colors (2-4096)');
  63. WriteLn(' -genmipmaps: generates mipmaps for main image');
  64. WriteLn(' argument: number of desired mip levels. 0 or no arg means');
  65. WriteLn(' create all possible levels');
  66. WriteLn(' -rotate: rotates input images counterclockwise');
  67. WriteLn(' argument: angle in degrees, multiple of 90');
  68. WriteLn(' Supported file formats: PNG, JPG, TGA, DDS, JNG, MNG, BMP');
  69. Write (' Supported data formats: ');
  70. for I := ifIndex8 to High(TImageFormat) do
  71. begin
  72. if Imaging.GetImageFormatInfo(I, Info) then
  73. Write(Info.Name, Iff(I <> High(TImageFormat), ', ', ''));
  74. end;
  75. end;
  76. procedure PrintError(const Msg: string; const Args: array of const);
  77. begin
  78. WriteLn(Format('Error: ' + Msg, Args));
  79. WriteLn;
  80. PrintUsage;
  81. Operations.Free;
  82. Halt(1);
  83. end;
  84. procedure PrintWarning(const Msg: string; const Args: array of const);
  85. begin
  86. WriteLn(Format('Warning: ' + Msg, Args));
  87. end;
  88. procedure PrintInfo(const Msg: string; const Args: array of const);
  89. begin
  90. WriteLn(Format('Info: ' + Msg, Args));
  91. end;
  92. procedure ParseCommandLine;
  93. var
  94. I: LongInt;
  95. procedure ParseOption(const Opt: string);
  96. var
  97. I: LongInt;
  98. S, Arg: string;
  99. begin
  100. S := Opt;
  101. I := Pos('=', S);
  102. if I > 0 then
  103. Arg := Copy(S, I + 1, MaxInt)
  104. else
  105. Arg := 'none';
  106. Delete(S, I, MaxInt);
  107. Delete(S, 1, 1);
  108. S := LowerCase(S);
  109. if S = 'infile' then
  110. InFile := Arg
  111. else if S = 'outfile' then
  112. OutFile := Arg
  113. else
  114. Operations.Add(Format('%s=%s', [S, LowerCase(Arg)]));
  115. end;
  116. begin
  117. for I := 1 to ParamCount do
  118. ParseOption(ParamStr(I));
  119. end;
  120. procedure CheckOptions;
  121. var
  122. InFileName, InFileDir: string;
  123. begin
  124. // Check if input and input filenames are valid
  125. if InFile = '' then
  126. PrintError('Input file not specified', []);
  127. if not FileExists(InFile) then
  128. PrintError('Input file not found: "%s"', [InFile]);
  129. if not Imaging.IsFileFormatSupported(InFile) then
  130. PrintError('Input file format not supported: %s', [ImagingUtility.GetFileExt(InFile)]);
  131. if OutFile = '' then
  132. begin
  133. PrintWarning('Output file not specified, using default: %s (in current directory)',
  134. [DefaultOutputFile]);
  135. OutFile := DefaultOutputFile;
  136. end;
  137. InFileName := ExtractFileName(InFile);
  138. InFileDir := ExtractFileDir(InFile);
  139. InFileDir := Iff(InFileDir <> '', PathDelim, InFileDir);
  140. // If outpout filename is in format "*.ext" then input filename is used
  141. // but with "ext" extension
  142. if ChangeFileExt(ExtractFileName(OutFile), '') = '*' then
  143. OutFile := InFileDir + ChangeFileExt(InFileName, ExtractFileExt(OutFile));
  144. if not Imaging.IsFileFormatSupported(OutFile) then
  145. begin
  146. PrintWarning('Output file format not supported, using default: %s',
  147. [DefaultFileFormat]);
  148. OutFile := InFileDir + ChangeFileExt(InFileName, '.' + DefaultFileFormat);
  149. end;
  150. end;
  151. procedure ProcessOperations;
  152. var
  153. I, J, X, Y, NewWidth, NewHeight: LongInt;
  154. OpName, Arg, S: string;
  155. Images: TDynImageDataArray;
  156. Format: TImageFormat;
  157. ResFilter: TResizeFilter;
  158. MainImage: TImageData;
  159. procedure PrintInvalidArg(const OpName, Arg: string);
  160. begin
  161. PrintError('Invalid argument (%s) for operation: %s', [Arg, OpName]);
  162. end;
  163. function FindFormat(const FmtString: string): TImageFormat;
  164. var
  165. I: TImageFormat;
  166. Name: string;
  167. begin
  168. Result := ifUnknown;
  169. for I := ifIndex8 to High(TImageFormat) do
  170. begin
  171. Name := Imaging.GetFormatName(I);
  172. if SameText(FmtString, Name) or SameText(FmtString, 'if' + Name) then
  173. begin
  174. Result := I;
  175. Exit;
  176. end;
  177. end;
  178. end;
  179. begin
  180. Operations.NameValueSeparator := '=';
  181. InitImage(MainImage);
  182. try
  183. // Load input image
  184. if not Imaging.LoadMultiImageFromFile(InFile, Images) then
  185. PrintError('Input file loading failed: %s', [ImagingUtility.GetExceptObject.Message]);
  186. // Check if all loaded images are OK or if they are any at all
  187. if (Length(Images) = 0) or not Imaging.TestImagesInArray(Images) then
  188. PrintError('Input file loaded but it does not contain any images or some of them are invalid', []);
  189. PrintInfo('Input images (count: %d) loaded succesfully from: %s', [Length(Images), InFile]);
  190. // Now process operations one by one
  191. for I := 0 to Operations.Count - 1 do
  192. begin
  193. // Get operation name and argument
  194. OpName := Operations.Names[I];
  195. Arg := Operations.ValueFromIndex[I];
  196. if OpName = 'format' then
  197. begin
  198. // Check if argument is name of some data format
  199. Format := FindFormat(Arg);
  200. if Format = ifUnknown then
  201. PrintInvalidArg(OpName, Arg);
  202. // If some format was found then all images are converted to it
  203. PrintInfo('Converting images to data format: %s', [Imaging.GetFormatName(Format)]);
  204. for J := 0 to High(Images) do
  205. Imaging.ConvertImage(Images[J], Format);
  206. end
  207. else if OpName = 'resize' then
  208. begin
  209. // Parse argument in format %dx%d[x%s]
  210. J := Pos('x', Arg);
  211. if J = 0 then
  212. PrintInvalidArg(OpName, Arg);
  213. X := StrToIntDef(Copy(Arg, 1, J - 1), Images[0].Width);
  214. Delete(Arg, 1, J);
  215. J := Pos('x', Arg);
  216. S := 'nearest';
  217. if J <> 0 then
  218. begin
  219. S := Copy(Arg, J + 1, MaxInt);
  220. Delete(Arg, J, MaxInt);
  221. end;
  222. Y := StrToIntDef(Arg, 0);
  223. // Limit new dimensions to 8192 and convert
  224. // invalid dimensions are set to 0 which is special value (later)
  225. X := ClampInt(X, 0, 8192);
  226. Y := ClampInt(Y, 0, 8192);
  227. // Select filtering method used for resizing according to argument
  228. ResFilter := rfNearest;
  229. if Pos('bil', S) = 1 then
  230. ResFilter := rfBilinear
  231. else if Pos('bic', S) = 1 then
  232. ResFilter := rfBicubic;
  233. PrintInfo('Resizing images to %dx%d using [%s] filter: ', [X, Y, S]);
  234. for J := 0 to High(Images) do
  235. begin
  236. // If any of new dimensions is 0 we use the original dimension
  237. // of image
  238. NewWidth := Iff(X = 0, Images[J].Width, X);
  239. NewHeight := Iff(Y = 0, Images[J].Height, Y);
  240. Imaging.ResizeImage(Images[J], NewWidth, NewHeight, ResFilter);
  241. end;
  242. end
  243. else if OpName = 'flip' then
  244. begin
  245. // Simply flip all images
  246. PrintInfo('Flipping images upside down', []);
  247. for J := 0 to High(Images) do
  248. Imaging.FlipImage(Images[J]);
  249. end
  250. else if OpName = 'mirror' then
  251. begin
  252. // Simply mirror all images
  253. PrintInfo('Mirroring images left to right', []);
  254. for J := 0 to High(Images) do
  255. Imaging.MirrorImage(Images[J]);
  256. end
  257. else if OpName = 'colorcount' then
  258. begin
  259. // Get value of the argument ...
  260. if not TryStrToInt(Arg, X) then
  261. PrintInvalidArg(OpName, Arg);
  262. X := ClampInt(X, 2, 4096);
  263. PrintInfo('Reducing color count of images to: %d', [X]);
  264. // ... and reduce number of colors of all images
  265. for J := 0 to High(Images) do
  266. Imaging.ReduceColors(Images[J], X);
  267. end
  268. else if OpName = 'genmipmaps' then
  269. begin
  270. // Get number of mipmaps from argument or use
  271. // default 0 which means "create all mip levels you can"
  272. X := StrToIntDef(Arg, 0);
  273. PrintInfo('Generating mipmaps for main image', []);
  274. // Clone main image and use input array as the output of
  275. // mipmap generation function
  276. Imaging.CloneImage(Images[0], MainImage);
  277. Imaging.GenerateMipMaps(MainImage, X, Images);
  278. end
  279. else if OpName = 'rotate' then
  280. begin
  281. // Parse argument, only multiples of 90 degrees are allowed
  282. if not TryStrToInt(Arg, X) then
  283. PrintInvalidArg(OpName, Arg);
  284. if X mod 90 <> 0 then
  285. PrintInvalidArg(OpName, Arg);
  286. PrintInfo('Rotating images: %d degrees CCW', [X]);
  287. // Rotate all
  288. for J := 0 to High(Images) do
  289. Imaging.RotateImage(Images[J], X);
  290. end
  291. else
  292. begin
  293. // Warn about unknown operations passed to program
  294. PrintWarning('Unrecognized operation: ' + OpName, []);
  295. end;
  296. end;
  297. // Finally save the result
  298. if not Imaging.SaveMultiImageToFile(OutFile, Images) then
  299. PrintError('Output file saving failed: %s', [ImagingUtility.GetExceptObject.Message])
  300. else
  301. PrintInfo('Output images saved succesfully to: %s', [OutFile])
  302. finally
  303. // Free images in array as well as temp image
  304. Imaging.FreeImagesInArray(Images);
  305. Imaging.FreeImage(MainImage);
  306. end;
  307. end;
  308. begin
  309. PrintHeader;
  310. Operations := TStringList.Create;
  311. ParseCommandLine;
  312. CheckOptions;
  313. try
  314. ProcessOperations;
  315. except
  316. PrintError('Exception raised during processing oprations: %s',
  317. [ImagingUtility.GetExceptObject.Message]);
  318. end;
  319. Operations.Free;
  320. {
  321. File Notes:
  322. -- TODOS ----------------------------------------------------
  323. - more operations
  324. - allow changing ImagingOptions too
  325. - make list of supported file formats dynamic as the data fmt list is
  326. -- 0.19 Changes/Bug Fixes -----------------------------------
  327. - demo created
  328. }
  329. end.