Repo for the search and displace ingest module that takes odf, docx and pdf and transforms it into .md to be used with search and displace operations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

443 lines
14 KiB

3 years ago
  1. {
  2. Deskew
  3. by Marek Mauder
  4. http://galfar.vevb.net/deskew
  5. The contents of this file are used with permission, subject to the Mozilla
  6. Public License Version 1.1 (the "License"); you may not use this file except
  7. in compliance with the License. You may obtain a copy of the License at
  8. http://www.mozilla.org/MPL/MPL-1.1.html
  9. Software distributed under the License is distributed on an "AS IS" basis,
  10. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  11. the specific language governing rights and limitations under the License.
  12. Alternatively, the contents of this file may be used under the terms of the
  13. GNU Lesser General Public License (the "LGPL License"), in which case the
  14. provisions of the LGPL License are applicable instead of those above.
  15. If you wish to allow use of your version of this file only under the terms
  16. of the LGPL License and not to allow others to use your version of this file
  17. under the MPL, indicate your decision by deleting the provisions above and
  18. replace them with the notice and other provisions required by the LGPL
  19. License. If you do not delete the provisions above, a recipient may use
  20. your version of this file under either the MPL or the LGPL License.
  21. For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
  22. }
  23. unit CmdLineOptions;
  24. interface
  25. uses
  26. {$IFNDEF FPC}
  27. Types,
  28. StrUtils,
  29. {$ENDIF}
  30. SysUtils,
  31. Classes,
  32. ImagingTypes,
  33. ImagingUtility,
  34. ImageUtils;
  35. const
  36. DefaultThreshold = 128;
  37. DefaultMaxAngle = 10;
  38. DefaultSkipAngle = 0.01;
  39. SDefaultOutputFile = 'out.png';
  40. type
  41. TThresholdingMethod = (
  42. // Use explicit threshold [0..255]
  43. tmExplicit,
  44. // Use adaptive thresholding: Otsu's method
  45. tmOtsu
  46. );
  47. TOperationalFlag = (
  48. ofAutoCrop,
  49. ofDetectOnly
  50. );
  51. TOperationalFlags = set of TOperationalFlag;
  52. TCmdLineOptions = class
  53. private
  54. FInputFile: string;
  55. FOutputFile: string;
  56. FMaxAngle: Double;
  57. FSkipAngle: Double;
  58. FResamplingFilter: TResamplingFilter;
  59. FThresholdingMethod: TThresholdingMethod;
  60. FThresholdLevel: Integer;
  61. FContentRect: TRect;
  62. FBackgroundColor: TColor32;
  63. FForcedOutputFormat: TImageFormat;
  64. FOperationalFlags: TOperationalFlags;
  65. FShowStats: Boolean;
  66. FShowParams: Boolean;
  67. FShowTimings: Boolean;
  68. FJpegCompressionQuality: Integer;
  69. FTiffCompressionScheme: Integer;
  70. FFormatSettings: TFormatSettings;
  71. FErrorMessage: string;
  72. function GetIsValid: Boolean;
  73. public
  74. constructor Create;
  75. // Parses command line arguments to get optiosn set by user
  76. function ParseCommnadLine: Boolean;
  77. function OptionsToString: string;
  78. property InputFile: string read FInputFile;
  79. property OutputFile: string read FOutputFile;
  80. // Max expected rotation angle - algo then works in range [-MaxAngle, MaxAngle]
  81. property MaxAngle: Double read FMaxAngle;
  82. // Skew threshold angle - skip deskewing if detected skew angle is in range (-MinAngle, MinAngle)
  83. property SkipAngle: Double read FSkipAngle;
  84. // Resampling filter used for rotations
  85. property ResamplingFilter: TResamplingFilter read FResamplingFilter;
  86. // Thresholding method used when converting images to binary black/white format
  87. property ThresholdingMethod: TThresholdingMethod read FThresholdingMethod;
  88. // Threshold for black/white pixel classification for explicit thresholding method
  89. property ThresholdLevel: Integer read FThresholdLevel;
  90. // Rect where to do the skew detection on the page image
  91. property ContentRect: TRect read FContentRect;
  92. // Background color for the rotated image
  93. property BackgroundColor: TColor32 read FBackgroundColor;
  94. // Forced output format (applied just before saving the output)
  95. property ForcedOutputFormat: TImageFormat read FForcedOutputFormat;
  96. // On/Off flags that control parts of the whole operation
  97. property OperationalFlags: TOperationalFlags read FOperationalFlags;
  98. // Show skew detection stats
  99. property ShowStats: Boolean read FShowStats;
  100. // Show current params to user (for testing etc.)
  101. property ShowParams: Boolean read FShowParams;
  102. // Show timing of processing steps to user
  103. property ShowTimings: Boolean read FShowTimings;
  104. // Compression quality of JPEG outputs (also embedded) in range [1, 100(best)]
  105. property JpegCompressionQuality: Integer read FJpegCompressionQuality;
  106. // Compression scheme of TIFF outputs. Values and default in imaginglib.
  107. property TiffCompressionScheme: Integer read FTiffCompressionScheme;
  108. property IsValid: Boolean read GetIsValid;
  109. property ErrorMessage: string read FErrorMessage;
  110. end;
  111. implementation
  112. uses
  113. TypInfo, Imaging, ImagingTiff;
  114. const
  115. TiffCompressionNames: array[TiffCompressionOptionNone..TiffCompressionOptionGroup4] of string = (
  116. 'none', 'lzw', 'rle', 'deflate', 'jpeg', 'g4'
  117. );
  118. { TCmdLineOptions }
  119. constructor TCmdLineOptions.Create;
  120. begin
  121. FThresholdLevel := DefaultThreshold;
  122. FMaxAngle := DefaultMaxAngle;
  123. FSkipAngle := DefaultSkipAngle;
  124. FResamplingFilter := rfLinear;
  125. FThresholdingMethod := tmOtsu;
  126. FContentRect := Rect(0, 0, 0, 0); // whole page
  127. FBackgroundColor := $FF000000;
  128. FOutputFile := SDefaultOutputFile;
  129. FOperationalFlags := [];
  130. FShowStats := False;
  131. FShowParams := False;
  132. FShowTimings:= False;
  133. FForcedOutputFormat := ifUnknown;
  134. FJpegCompressionQuality := -1; // use imaginglib default
  135. FTiffCompressionScheme := -1; // use imaginglib default
  136. FFormatSettings := ImagingUtility.GetFormatSettingsForFloats;
  137. end;
  138. function TCmdLineOptions.GetIsValid: Boolean;
  139. begin
  140. Result := (InputFile <> '') and (MaxAngle > 0) and (SkipAngle >= 0) and
  141. ((ThresholdingMethod in [tmOtsu]) or (ThresholdingMethod = tmExplicit) and (ThresholdLevel > 0));
  142. end;
  143. function TCmdLineOptions.ParseCommnadLine: Boolean;
  144. var
  145. I: LongInt;
  146. Param, Arg: string;
  147. // From Delphi RTL StrUtils.pas - for compiling in Delphi 7
  148. function SplitString(const S, Delimiters: string): TDynStringArray;
  149. var
  150. StartIdx: Integer;
  151. FoundIdx: Integer;
  152. SplitPoints: Integer;
  153. CurrentSplit: Integer;
  154. I: Integer;
  155. function FindDelimiter(const Delimiters, S: string; StartIdx: Integer = 1): Integer;
  156. var
  157. Stop: Boolean;
  158. Len: Integer;
  159. begin
  160. Result := 0;
  161. Len := Length(S);
  162. Stop := False;
  163. while (not Stop) and (StartIdx <= Len) do
  164. if IsDelimiter(Delimiters, S, StartIdx) then
  165. begin
  166. Result := StartIdx;
  167. Stop := True;
  168. end
  169. else
  170. Inc(StartIdx);
  171. end;
  172. begin
  173. Result := nil;
  174. if S <> '' then
  175. begin
  176. SplitPoints := 0;
  177. for I := 1 to Length(S) do
  178. begin
  179. if IsDelimiter(Delimiters, S, I) then
  180. Inc(SplitPoints);
  181. end;
  182. SetLength(Result, SplitPoints + 1);
  183. StartIdx := 1;
  184. CurrentSplit := 0;
  185. repeat
  186. FoundIdx := FindDelimiter(Delimiters, S, StartIdx);
  187. if FoundIdx <> 0 then
  188. begin
  189. Result[CurrentSplit] := Copy(S, StartIdx, FoundIdx - StartIdx);
  190. Inc(CurrentSplit);
  191. StartIdx := FoundIdx + 1;
  192. end;
  193. until CurrentSplit = SplitPoints;
  194. Result[SplitPoints] := Copy(S, StartIdx, Length(S) - StartIdx + 1);
  195. end;
  196. end;
  197. function CheckParam(const Param, Value: string): Boolean;
  198. var
  199. StrArray: TDynStringArray;
  200. ValLower, S: string;
  201. TempColor: Cardinal;
  202. Val64: Int64;
  203. I, J: Integer;
  204. begin
  205. Result := True;
  206. ValLower := LowerCase(Value);
  207. if Param = '-o' then
  208. FOutputFile := Value
  209. else if Param = '-a' then
  210. begin
  211. if not TryStrToFloat(Value, FMaxAngle, FFormatSettings) then
  212. FErrorMessage := 'Invalid value for max angle parameter: ' + Value;
  213. end
  214. else if Param = '-l' then
  215. begin
  216. if not TryStrToFloat(Value, FSkipAngle, FFormatSettings) then
  217. FErrorMessage := 'Invalid value for skip angle parameter: ' + Value;
  218. end
  219. else if Param = '-t' then
  220. begin
  221. if ValLower = 'a' then
  222. FThresholdingMethod := tmOtsu
  223. else
  224. begin
  225. FThresholdingMethod := tmExplicit;
  226. if not TryStrToInt(Value, FThresholdLevel) then
  227. FErrorMessage := 'Invalid value for treshold parameter: ' + Value;
  228. end;
  229. end
  230. else if Param = '-b' then
  231. begin
  232. if TryStrToInt64('$' + ValLower, Val64) then
  233. begin
  234. TempColor := Cardinal(Val64 and $FFFFFFFF);
  235. if TempColor <= $FF then
  236. begin
  237. // Just one channel given, replicate for all channels + opaque
  238. FBackgroundColor := Color32($FF, Byte(TempColor), Byte(TempColor), Byte(TempColor)).Color;
  239. end
  240. else if (TempColor <= $FFFFFF) and (Length(ValLower) <= 6) then
  241. begin
  242. // RGB given, set alpha to 255 for background
  243. FBackgroundColor := $FF000000 or TempColor;
  244. end
  245. else
  246. begin
  247. // Full ARGB given
  248. FBackgroundColor := TempColor;
  249. end;
  250. end
  251. else
  252. FErrorMessage := 'Invalid value for background color parameter: ' + Value;
  253. end
  254. else if Param = '-f' then
  255. begin
  256. if ValLower = 'b1' then
  257. FForcedOutputFormat := ifBinary
  258. else if ValLower = 'g8' then
  259. FForcedOutputFormat := ifGray8
  260. else if ValLower = 'rgb24' then
  261. FForcedOutputFormat := ifR8G8B8
  262. else if ValLower = 'rgba32' then
  263. FForcedOutputFormat := ifA8R8G8B8
  264. else
  265. FErrorMessage := 'Invalid value for format parameter: ' + Value;
  266. end
  267. else if Param = '-q' then
  268. begin
  269. if ValLower = 'nearest' then
  270. FResamplingFilter := rfNearest
  271. else if ValLower = 'linear' then
  272. FResamplingFilter := rfLinear
  273. else if ValLower = 'cubic' then
  274. FResamplingFilter := rfCubic
  275. else if ValLower = 'lanczos' then
  276. FResamplingFilter := rfLanczos
  277. else
  278. FErrorMessage := 'Invalid value for resampling filter parameter: ' + Value;
  279. end
  280. else if Param = '-g' then
  281. begin
  282. if Pos('c', ValLower) > 0 then
  283. Include(FOperationalFlags, ofAutoCrop);
  284. if Pos('d', ValLower) > 0 then
  285. Include(FOperationalFlags, ofDetectOnly);
  286. end
  287. else if Param = '-s' then
  288. begin
  289. if Pos('s', ValLower) > 0 then
  290. FShowStats := True;
  291. if Pos('p', ValLower) > 0 then
  292. FShowParams := True;
  293. if Pos('t', ValLower) > 0 then
  294. FShowTimings := True;
  295. end
  296. else if Param = '-r' then
  297. begin
  298. StrArray := SplitString(ValLower, ',');
  299. if Length(StrArray) = 4 then
  300. begin
  301. FContentRect.Left := StrToInt(StrArray[0]);
  302. FContentRect.Top := StrToInt(StrArray[1]);
  303. FContentRect.Right := StrToInt(StrArray[2]);
  304. FContentRect.Bottom := StrToInt(StrArray[3]);
  305. end;
  306. end
  307. else if Param = '-c' then
  308. begin
  309. StrArray := SplitString(ValLower, ',');
  310. for I := 0 to High(StrArray) do
  311. begin
  312. S := StrArray[I];
  313. if Pos('t', S) = 1 then
  314. begin
  315. S := Copy(S, 2);
  316. FTiffCompressionScheme := -1;
  317. for J := Low(TiffCompressionNames) to High(TiffCompressionNames) do
  318. begin
  319. if S = TiffCompressionNames[J] then
  320. begin
  321. FTiffCompressionScheme := J;
  322. Break;
  323. end;
  324. end;
  325. if FTiffCompressionScheme = -1 then
  326. begin
  327. FErrorMessage := 'Invalid TIFF output compression spec: ' + S;
  328. Exit(False);
  329. end;
  330. end
  331. else if Pos('j', S) = 1 then
  332. begin
  333. S := Copy(S, 2);
  334. if not TryStrToInt(S,FJpegCompressionQuality) then
  335. begin
  336. FErrorMessage := 'Invalid JPEG output compression spec: ' + S;
  337. Exit(False);
  338. end;
  339. end
  340. else
  341. begin
  342. FErrorMessage := 'Invalid output compression parameter: ' + S;
  343. Exit(False);
  344. end;
  345. end;
  346. end
  347. else
  348. begin
  349. FErrorMessage := 'Unknown parameter: ' + Param;
  350. end;
  351. if FErrorMessage <> '' then
  352. Result := False;
  353. end;
  354. begin
  355. Result := True;
  356. I := 1;
  357. while I <= ParamCount do
  358. begin
  359. Param := ParamStr(I);
  360. if Pos('-', Param) = 1 then
  361. begin
  362. Arg := ParamStr(I + 1);
  363. Inc(I);
  364. if not CheckParam(Param, Arg) then
  365. begin
  366. Result := False;
  367. Exit;
  368. end;
  369. end
  370. else
  371. FInputFile := Param;
  372. Inc(I);
  373. end;
  374. if FInputFile = '' then
  375. FErrorMessage := 'No input file given';
  376. end;
  377. function TCmdLineOptions.OptionsToString: string;
  378. var
  379. I: Integer;
  380. CompJpegStr, CompTiffStr, FilterStr, CmdParams: string;
  381. begin
  382. CmdParams := '';
  383. for I := 1 to ParamCount do
  384. CmdParams := CmdParams + ParamStr(I) + ' ';
  385. FilterStr := LowerCase(Copy(TypInfo.GetEnumName(TypeInfo(TResamplingFilter), Integer(FResamplingFilter)), 3));
  386. CompJpegStr := Iff(JpegCompressionQuality = -1, 'default', IntToStr(JpegCompressionQuality));
  387. CompTiffStr := 'default';
  388. if TiffCompressionScheme >= 0 then
  389. CompTiffStr := TiffCompressionNames[TiffCompressionScheme];
  390. Result :=
  391. 'Parameters: ' + CmdParams + sLineBreak +
  392. ' input file = ' + InputFile + sLineBreak +
  393. ' output file = ' + OutputFile + sLineBreak +
  394. ' max angle = ' + FloatToStr(MaxAngle) + sLineBreak +
  395. ' background color = ' + IntToHex(BackgroundColor, 8) + sLineBreak +
  396. ' resampling filter = ' + FilterStr + sLineBreak +
  397. ' thresholding method = ' + Iff(ThresholdingMethod = tmExplicit, 'explicit', 'auto otsu') + sLineBreak +
  398. ' threshold level = ' + IntToStr(ThresholdLevel) + sLineBreak +
  399. ' content rect = ' + Format('%d,%d,%d,%d', [ContentRect.Left, ContentRect.Top, ContentRect.Right, ContentRect.Bottom]) + sLineBreak +
  400. ' output format = ' + Iff(ForcedOutputFormat = ifUnknown, 'default', Imaging.GetFormatName(ForcedOutputFormat)) + sLineBreak +
  401. ' skip angle = ' + FloatToStr(SkipAngle) + sLineBreak +
  402. ' oper flags = ' + Iff(ofAutoCrop in FOperationalFlags, 'auto-crop ', '') + Iff(ofDetectOnly in FOperationalFlags, 'detect-only ', '') + sLineBreak +
  403. ' show info = ' + Iff(ShowParams, 'params ', '') + Iff(ShowStats, 'stats ', '') + Iff(ShowTimings, 'timings ', '') + sLineBreak +
  404. ' output compression = jpeg:' + CompJpegStr + ' tiff:' + CompTiffStr + sLineBreak;
  405. end;
  406. end.