svg
Browse files- image/convert.html +1 -0
- image/converter.py +64 -14
- image/image_base.py +2 -1
image/convert.html
CHANGED
|
@@ -520,6 +520,7 @@
|
|
| 520 |
<option value="jpg">JPG</option>
|
| 521 |
<option value="png">PNG</option>
|
| 522 |
<option value="webp">WebP</option>
|
|
|
|
| 523 |
</select>
|
| 524 |
</div>
|
| 525 |
</div>
|
|
|
|
| 520 |
<option value="jpg">JPG</option>
|
| 521 |
<option value="png">PNG</option>
|
| 522 |
<option value="webp">WebP</option>
|
| 523 |
+
<option value="svg">SVG</option>
|
| 524 |
</select>
|
| 525 |
</div>
|
| 526 |
</div>
|
image/converter.py
CHANGED
|
@@ -8,6 +8,8 @@ and aspect ratio during format conversion. Supports all major image formats.
|
|
| 8 |
from .image_base import ImageBase
|
| 9 |
from PIL import Image
|
| 10 |
import os
|
|
|
|
|
|
|
| 11 |
from typing import Dict, Optional, Tuple
|
| 12 |
from custom_logger import logger_config
|
| 13 |
import pillow_heif
|
|
@@ -165,6 +167,49 @@ class Converter(ImageBase):
|
|
| 165 |
|
| 166 |
return converted_image
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
def _verify_conversion_quality(self, original_size: Tuple[int, int], output_path: str) -> bool:
|
| 169 |
"""
|
| 170 |
Verify that the converted image maintains original quality and dimensions.
|
|
@@ -238,20 +283,25 @@ class Converter(ImageBase):
|
|
| 238 |
# Generate output path
|
| 239 |
output_path = self._generate_output_path(output_format)
|
| 240 |
|
| 241 |
-
#
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
logger_config.info(f"β Conversion completed successfully: {output_path}")
|
| 257 |
return output_path
|
|
|
|
| 8 |
from .image_base import ImageBase
|
| 9 |
from PIL import Image
|
| 10 |
import os
|
| 11 |
+
import base64
|
| 12 |
+
import io
|
| 13 |
from typing import Dict, Optional, Tuple
|
| 14 |
from custom_logger import logger_config
|
| 15 |
import pillow_heif
|
|
|
|
| 167 |
|
| 168 |
return converted_image
|
| 169 |
|
| 170 |
+
def _convert_to_svg(self, image: Image.Image, output_path: str, original_size: Tuple[int, int]) -> None:
|
| 171 |
+
"""
|
| 172 |
+
Convert a raster image to SVG by embedding it as a base64-encoded image.
|
| 173 |
+
|
| 174 |
+
Args:
|
| 175 |
+
image: PIL Image object
|
| 176 |
+
output_path: Path to save the SVG file
|
| 177 |
+
original_size: Original image dimensions (width, height)
|
| 178 |
+
"""
|
| 179 |
+
# Convert image to PNG bytes for embedding
|
| 180 |
+
buffer = io.BytesIO()
|
| 181 |
+
|
| 182 |
+
# Ensure we save in a format that preserves transparency
|
| 183 |
+
if image.mode in ('RGBA', 'LA', 'P'):
|
| 184 |
+
image.save(buffer, format='PNG', optimize=False)
|
| 185 |
+
mime_type = 'image/png'
|
| 186 |
+
else:
|
| 187 |
+
image.save(buffer, format='PNG', optimize=False)
|
| 188 |
+
mime_type = 'image/png'
|
| 189 |
+
|
| 190 |
+
buffer.seek(0)
|
| 191 |
+
image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
| 192 |
+
|
| 193 |
+
width, height = original_size
|
| 194 |
+
|
| 195 |
+
# Create SVG with embedded image
|
| 196 |
+
svg_content = f'''<?xml version="1.0" encoding="UTF-8"?>
|
| 197 |
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
| 198 |
+
width="{width}" height="{height}" viewBox="0 0 {width} {height}">
|
| 199 |
+
<image width="{width}" height="{height}"
|
| 200 |
+
xlink:href="data:{mime_type};base64,{image_base64}"/>
|
| 201 |
+
</svg>'''
|
| 202 |
+
|
| 203 |
+
with open(output_path, 'w', encoding='utf-8') as f:
|
| 204 |
+
f.write(svg_content)
|
| 205 |
+
|
| 206 |
+
# Verify file was created
|
| 207 |
+
if not os.path.exists(output_path):
|
| 208 |
+
raise Exception("SVG file was not created")
|
| 209 |
+
|
| 210 |
+
file_size = os.path.getsize(output_path)
|
| 211 |
+
logger_config.info(f"β SVG file created: {file_size:,} bytes")
|
| 212 |
+
|
| 213 |
def _verify_conversion_quality(self, original_size: Tuple[int, int], output_path: str) -> bool:
|
| 214 |
"""
|
| 215 |
Verify that the converted image maintains original quality and dimensions.
|
|
|
|
| 283 |
# Generate output path
|
| 284 |
output_path = self._generate_output_path(output_format)
|
| 285 |
|
| 286 |
+
# Handle SVG conversion specially
|
| 287 |
+
if target_format == 'SVG':
|
| 288 |
+
logger_config.info(f"Converting to SVG (embedding as base64)")
|
| 289 |
+
self._convert_to_svg(converted_image, output_path, original_size)
|
| 290 |
+
else:
|
| 291 |
+
# Get optimal save settings
|
| 292 |
+
save_settings = self._get_optimal_save_settings(target_format)
|
| 293 |
+
|
| 294 |
+
# Preserve metadata
|
| 295 |
+
metadata = self._preserve_metadata(original_image, target_format)
|
| 296 |
+
save_settings.update(metadata)
|
| 297 |
+
|
| 298 |
+
logger_config.info(f"Converting to {target_format} with maximum quality")
|
| 299 |
+
|
| 300 |
+
# Perform conversion with quality preservation
|
| 301 |
+
converted_image.save(output_path, format=target_format, **save_settings)
|
| 302 |
+
|
| 303 |
+
# Verify conversion quality
|
| 304 |
+
self._verify_conversion_quality(original_size, output_path)
|
| 305 |
|
| 306 |
logger_config.info(f"β Conversion completed successfully: {output_path}")
|
| 307 |
return output_path
|
image/image_base.py
CHANGED
|
@@ -21,7 +21,8 @@ class ImageBase(MainBase):
|
|
| 21 |
'tif': 'TIFF',
|
| 22 |
'bmp': 'BMP',
|
| 23 |
'gif': 'GIF',
|
| 24 |
-
'heic': 'HEIC'
|
|
|
|
| 25 |
}
|
| 26 |
|
| 27 |
# Create directories if they don't exist
|
|
|
|
| 21 |
'tif': 'TIFF',
|
| 22 |
'bmp': 'BMP',
|
| 23 |
'gif': 'GIF',
|
| 24 |
+
'heic': 'HEIC',
|
| 25 |
+
'svg': 'SVG'
|
| 26 |
}
|
| 27 |
|
| 28 |
# Create directories if they don't exist
|