jebin2 commited on
Commit
a638e13
·
1 Parent(s): 7c78840
__pycache__/custom_logger.cpython-313.pyc ADDED
Binary file (913 Bytes). View file
 
custom_logger.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ class LoggerConfig:
2
+ def info(self, msg):
3
+ print(f"INFO: {msg}")
4
+ def warning(self, msg):
5
+ print(f"WARNING: {msg}")
6
+ def error(self, msg):
7
+ print(f"ERROR: {msg}")
8
+
9
+ logger_config = LoggerConfig()
image/convert.html CHANGED
@@ -472,6 +472,16 @@
472
  <option value="gif">GIF</option>
473
  </select>
474
  </div>
 
 
 
 
 
 
 
 
 
 
475
  </div>
476
  </div>
477
 
@@ -505,6 +515,14 @@
505
  function secret_sauce_url() {
506
  return '/image/convert';
507
  }
 
 
 
 
 
 
 
 
508
  </script>
509
  </body>
510
 
 
472
  <option value="gif">GIF</option>
473
  </select>
474
  </div>
475
+ <div class="setting-group">
476
+ <label class="setting-label">Quality: <span id="quality-value">100</span>%</label>
477
+ <input type="range" class="setting-input" id="quality-slider" min="1" max="100" value="100"
478
+ style="padding: 0;">
479
+ </div>
480
+ <div class="setting-group">
481
+ <label class="setting-label">Resize: <span id="scale-value">100</span>%</label>
482
+ <input type="range" class="setting-input" id="scale-slider" min="10" max="100" value="100"
483
+ step="10" style="padding: 0;">
484
+ </div>
485
  </div>
486
  </div>
487
 
 
515
  function secret_sauce_url() {
516
  return '/image/convert';
517
  }
518
+
519
+ // Update slider values
520
+ document.getElementById('quality-slider').addEventListener('input', function (e) {
521
+ document.getElementById('quality-value').textContent = e.target.value;
522
+ });
523
+ document.getElementById('scale-slider').addEventListener('input', function (e) {
524
+ document.getElementById('scale-value').textContent = e.target.value;
525
+ });
526
  </script>
527
  </body>
528
 
image/converter.py CHANGED
@@ -50,12 +50,13 @@ class Converter(ImageBase):
50
 
51
  return self.supported_formats[format_clean]
52
 
53
- def _get_optimal_save_settings(self, format_name: str) -> Dict:
54
  """
55
  Get optimal save settings for maximum quality preservation.
56
 
57
  Args:
58
  format_name: PIL format name (e.g., 'JPEG', 'PNG')
 
59
 
60
  Returns:
61
  Dictionary of save settings for the format
@@ -64,28 +65,28 @@ class Converter(ImageBase):
64
 
65
  if format_name == 'JPEG':
66
  settings = {
67
- 'quality': 100, # Maximum quality
68
- 'optimize': False, # Don't optimize (can reduce quality)
69
- 'progressive': False, # Standard baseline JPEG
70
- 'subsampling': 0 # No chroma subsampling (4:4:4)
71
  }
72
 
73
  elif format_name == 'PNG':
74
  settings = {
75
- 'optimize': False, # Don't optimize to preserve exact quality
76
- 'compress_level': 1 # Minimal compression for maximum quality
77
  }
78
 
79
  elif format_name == 'WEBP':
80
  settings = {
81
- 'lossless': True, # Lossless compression
82
- 'quality': 100, # Maximum quality (for fallback)
83
- 'method': 6 # Best compression method
84
  }
85
 
86
  elif format_name == 'TIFF':
87
  settings = {
88
- 'compression': None # No compression for perfect quality
89
  }
90
 
91
  elif format_name == 'BMP':
@@ -93,12 +94,12 @@ class Converter(ImageBase):
93
 
94
  elif format_name == 'GIF':
95
  settings = {
96
- 'optimize': False # Don't optimize palette
97
  }
98
 
99
  elif format_name == 'HEIC':
100
  settings = {
101
- 'quality': 100 # Maximum quality for HEIC
102
  }
103
 
104
  return settings
@@ -215,13 +216,14 @@ class Converter(ImageBase):
215
  file_size = os.path.getsize(output_path)
216
  logger_config.info(f"✓ SVG file created: {file_size:,} bytes")
217
 
218
- def _verify_conversion_quality(self, original_size: Tuple[int, int], output_path: str) -> bool:
219
  """
220
  Verify that the converted image maintains original quality and dimensions.
221
 
222
  Args:
223
  original_size: Original image dimensions (width, height)
224
  output_path: Path to converted image
 
225
 
226
  Returns:
227
  True if verification passes
@@ -236,22 +238,28 @@ class Converter(ImageBase):
236
  file_size = os.path.getsize(output_path)
237
  logger_config.info(f"✓ Output file created: {file_size:,} bytes")
238
 
 
 
 
239
  # Verify dimensions are preserved
240
  with Image.open(output_path) as verify_img:
241
- if verify_img.size != original_size:
242
- raise Exception(f"Dimensions changed! Expected: {original_size}, Got: {verify_img.size}")
 
243
 
244
- logger_config.info(f"✓ Aspect ratio preserved: {verify_img.size}")
245
 
246
  return True
247
 
248
- def convert_image(self, input_file_name: str, output_format: str) -> str:
249
  """
250
  Convert an image to the specified format with maximum quality and ratio preservation.
251
 
252
  Args:
253
  input_file: Path to the input image file
254
  output_format: Target image format
 
 
255
 
256
  Returns:
257
  Path to the converted output file
@@ -268,7 +276,7 @@ class Converter(ImageBase):
268
  self._validate_input_file()
269
  target_format = self._validate_output_format(output_format)
270
 
271
- logger_config.info(f"Starting conversion: {self.input_file_path}")
272
 
273
  # Open and analyze original image
274
  with Image.open(self.input_file_path) as original_image:
@@ -281,9 +289,11 @@ class Converter(ImageBase):
281
  # Convert color mode if necessary
282
  converted_image = self._convert_color_mode(original_image, target_format)
283
 
284
- # Verify size preservation (safety check)
285
- if converted_image.size != original_size:
286
- raise Exception(f"Size changed during conversion! Original: {original_size}, Current: {converted_image.size}")
 
 
287
 
288
  # Generate output path
289
  output_path = self._generate_output_path(output_format)
@@ -291,22 +301,22 @@ class Converter(ImageBase):
291
  # Handle SVG conversion specially
292
  if target_format == 'SVG':
293
  logger_config.info(f"Converting to SVG (embedding as base64)")
294
- self._convert_to_svg(converted_image, output_path, original_size)
295
  else:
296
  # Get optimal save settings
297
- save_settings = self._get_optimal_save_settings(target_format)
298
 
299
  # Preserve metadata
300
  metadata = self._preserve_metadata(original_image, target_format)
301
  save_settings.update(metadata)
302
 
303
- logger_config.info(f"Converting to {target_format} with maximum quality")
304
 
305
  # Perform conversion with quality preservation
306
  converted_image.save(output_path, format=target_format, **save_settings)
307
 
308
  # Verify conversion quality
309
- self._verify_conversion_quality(original_size, output_path)
310
 
311
  logger_config.info(f"✓ Conversion completed successfully: {output_path}")
312
  return output_path
@@ -318,4 +328,4 @@ class Converter(ImageBase):
318
  # Example usage
319
  if __name__ == "__main__":
320
  converter = Converter()
321
- converter.convert_image("image/test.HEIC", "png")
 
50
 
51
  return self.supported_formats[format_clean]
52
 
53
+ def _get_optimal_save_settings(self, format_name: str, quality: int = 100) -> Dict:
54
  """
55
  Get optimal save settings for maximum quality preservation.
56
 
57
  Args:
58
  format_name: PIL format name (e.g., 'JPEG', 'PNG')
59
+ quality: Quality setting (1-100)
60
 
61
  Returns:
62
  Dictionary of save settings for the format
 
65
 
66
  if format_name == 'JPEG':
67
  settings = {
68
+ 'quality': quality,
69
+ 'optimize': True,
70
+ 'progressive': True,
71
+ 'subsampling': 0
72
  }
73
 
74
  elif format_name == 'PNG':
75
  settings = {
76
+ 'optimize': True,
77
+ 'compress_level': 9 if quality < 100 else 1
78
  }
79
 
80
  elif format_name == 'WEBP':
81
  settings = {
82
+ 'lossless': quality == 100,
83
+ 'quality': quality,
84
+ 'method': 6
85
  }
86
 
87
  elif format_name == 'TIFF':
88
  settings = {
89
+ 'compression': 'tiff_lzw' if quality < 100 else None
90
  }
91
 
92
  elif format_name == 'BMP':
 
94
 
95
  elif format_name == 'GIF':
96
  settings = {
97
+ 'optimize': True
98
  }
99
 
100
  elif format_name == 'HEIC':
101
  settings = {
102
+ 'quality': quality
103
  }
104
 
105
  return settings
 
216
  file_size = os.path.getsize(output_path)
217
  logger_config.info(f"✓ SVG file created: {file_size:,} bytes")
218
 
219
+ def _verify_conversion_quality(self, original_size: Tuple[int, int], output_path: str, scale: float = 1.0) -> bool:
220
  """
221
  Verify that the converted image maintains original quality and dimensions.
222
 
223
  Args:
224
  original_size: Original image dimensions (width, height)
225
  output_path: Path to converted image
226
+ scale: Scale factor used
227
 
228
  Returns:
229
  True if verification passes
 
238
  file_size = os.path.getsize(output_path)
239
  logger_config.info(f"✓ Output file created: {file_size:,} bytes")
240
 
241
+ # Calculate expected size
242
+ expected_size = (int(original_size[0] * scale), int(original_size[1] * scale))
243
+
244
  # Verify dimensions are preserved
245
  with Image.open(output_path) as verify_img:
246
+ # Allow for slight rounding differences (±1 pixel)
247
+ if abs(verify_img.size[0] - expected_size[0]) > 1 or abs(verify_img.size[1] - expected_size[1]) > 1:
248
+ raise Exception(f"Dimensions changed! Expected: {expected_size}, Got: {verify_img.size}")
249
 
250
+ logger_config.info(f"✓ Dimensions verified: {verify_img.size}")
251
 
252
  return True
253
 
254
+ def convert_image(self, input_file_name: str, output_format: str, quality: int = 100, scale: float = 1.0) -> str:
255
  """
256
  Convert an image to the specified format with maximum quality and ratio preservation.
257
 
258
  Args:
259
  input_file: Path to the input image file
260
  output_format: Target image format
261
+ quality: Quality setting (1-100)
262
+ scale: Scale factor (0.1-1.0)
263
 
264
  Returns:
265
  Path to the converted output file
 
276
  self._validate_input_file()
277
  target_format = self._validate_output_format(output_format)
278
 
279
+ logger_config.info(f"Starting conversion: {self.input_file_path} (Quality: {quality}, Scale: {scale})")
280
 
281
  # Open and analyze original image
282
  with Image.open(self.input_file_path) as original_image:
 
289
  # Convert color mode if necessary
290
  converted_image = self._convert_color_mode(original_image, target_format)
291
 
292
+ # Apply scaling if needed
293
+ if scale < 1.0:
294
+ new_size = (int(original_size[0] * scale), int(original_size[1] * scale))
295
+ logger_config.info(f"Resizing image to {new_size}")
296
+ converted_image = converted_image.resize(new_size, Image.Resampling.LANCZOS)
297
 
298
  # Generate output path
299
  output_path = self._generate_output_path(output_format)
 
301
  # Handle SVG conversion specially
302
  if target_format == 'SVG':
303
  logger_config.info(f"Converting to SVG (embedding as base64)")
304
+ self._convert_to_svg(converted_image, output_path, converted_image.size)
305
  else:
306
  # Get optimal save settings
307
+ save_settings = self._get_optimal_save_settings(target_format, quality)
308
 
309
  # Preserve metadata
310
  metadata = self._preserve_metadata(original_image, target_format)
311
  save_settings.update(metadata)
312
 
313
+ logger_config.info(f"Converting to {target_format} with quality {quality}")
314
 
315
  # Perform conversion with quality preservation
316
  converted_image.save(output_path, format=target_format, **save_settings)
317
 
318
  # Verify conversion quality
319
+ self._verify_conversion_quality(original_size, output_path, scale)
320
 
321
  logger_config.info(f"✓ Conversion completed successfully: {output_path}")
322
  return output_path
 
328
  # Example usage
329
  if __name__ == "__main__":
330
  converter = Converter()
331
+ converter.convert_image("image/test.HEIC", "png", quality=80, scale=0.5)
image/input/temp.temp DELETED
File without changes
image/input/test_quality.jpg ADDED
image/javascript/image.js CHANGED
@@ -414,7 +414,9 @@ async function upload(file) {
414
 
415
  function getSettingsData() {
416
  return {
417
- to_format: document.getElementById('output-format')?.value
 
 
418
  };
419
  }
420
 
 
414
 
415
  function getSettingsData() {
416
  return {
417
+ to_format: document.getElementById('output-format')?.value,
418
+ quality: document.getElementById('quality-slider')?.value,
419
+ scale: document.getElementById('scale-slider')?.value ? document.getElementById('scale-slider').value / 100 : null
420
  };
421
  }
422
 
image/output/temp.temp DELETED
File without changes
image/output/test_quality.jpeg ADDED
image/test_quality.jpg ADDED
main.py CHANGED
@@ -192,7 +192,9 @@ async def upload_image(
192
  @app.post("/image/convert")
193
  async def convert_image(
194
  id: str = Form(...),
195
- to_format: str = Form(...)
 
 
196
  ):
197
  try:
198
  # Check if input is SVG - PIL cannot read SVG files
@@ -200,7 +202,7 @@ async def convert_image(
200
  raise ValueError("SVG files cannot be converted. SVG is only available as an output format.")
201
 
202
  converter = Converter()
203
- output_path = converter.convert_image(id, to_format)
204
 
205
  # Check if conversion failed
206
  if output_path is None:
 
192
  @app.post("/image/convert")
193
  async def convert_image(
194
  id: str = Form(...),
195
+ to_format: str = Form(...),
196
+ quality: int = Form(100),
197
+ scale: float = Form(1.0)
198
  ):
199
  try:
200
  # Check if input is SVG - PIL cannot read SVG files
 
202
  raise ValueError("SVG files cannot be converted. SVG is only available as an output format.")
203
 
204
  converter = Converter()
205
+ output_path = converter.convert_image(id, to_format, quality=quality, scale=scale)
206
 
207
  # Check if conversion failed
208
  if output_path is None:
test_convert_options.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import os
3
+
4
+ url = "http://localhost:8000/image/convert"
5
+ input_dir = "image/input"
6
+ file_path = os.path.join(input_dir, "test_quality.jpg")
7
+ output_dir = "image/output"
8
+
9
+ # Ensure directories exist
10
+ os.makedirs(input_dir, exist_ok=True)
11
+ os.makedirs(output_dir, exist_ok=True)
12
+
13
+ # Create test image
14
+ from PIL import Image
15
+ Image.new('RGB', (100, 100), color='red').save(file_path)
16
+
17
+ # Test case 1: Quality 50, Scale 0.5
18
+ data = {
19
+ "id": "test_quality.jpg",
20
+ "to_format": "jpg",
21
+ "quality": 50,
22
+ "scale": 0.5
23
+ }
24
+
25
+ try:
26
+ response = requests.post(url, data=data)
27
+ if response.status_code == 200:
28
+ result = response.json()
29
+ print(f"Success: {result}")
30
+
31
+ # Verify output file
32
+ output_file = os.path.join(output_dir, result['new_filename'])
33
+ if os.path.exists(output_file):
34
+ print(f"Output file created: {output_file}")
35
+ print(f"Output file size: {os.path.getsize(output_file)} bytes")
36
+ else:
37
+ print("Error: Output file not found")
38
+ else:
39
+ print(f"Failed: {response.status_code} - {response.text}")
40
+ except Exception as e:
41
+ print(f"Error: {e}")