Skip to content

Commit e9f9aac

Browse files
Image compresson configured
1 parent 66ac108 commit e9f9aac

File tree

5 files changed

+64
-17
lines changed

5 files changed

+64
-17
lines changed

nxtbn/filemanager/api/dashboard/serializers.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
from django.utils.translation import gettext_lazy as _
22
from rest_framework import serializers
3+
from nxtbn import settings
34
from nxtbn.filemanager.models import Image, Document
45

56

67
from PIL import Image as PILImage
78
from io import BytesIO
89
from django.core.files.base import ContentFile
910

11+
1012
class ImageSerializer(serializers.ModelSerializer):
1113
class Meta:
1214
model = Image
1315
fields = "__all__"
14-
read_only_fields = (
15-
"id",
16-
"created_by",
17-
"last_modified_by",
18-
)
16+
read_only_fields = ("id", "created_by", "last_modified_by")
1917

2018
def create(self, validated_data):
2119
request = self.context["request"]
@@ -25,6 +23,7 @@ def create(self, validated_data):
2523
# Optimize the image before saving
2624
if "image" in validated_data:
2725
validated_data["image"] = self.optimize_image(validated_data["image"])
26+
validated_data["image_xs"] = self.optimize_image(validated_data["image"], max_size_kb=1, format="png", max_dimension=50)
2827

2928
return super().create(validated_data)
3029

@@ -39,28 +38,38 @@ def update(self, instance, validated_data):
3938
return super().update(instance, validated_data)
4039

4140
@staticmethod
42-
def optimize_image(image_file, max_size_kb=200, format="WEBP"):
41+
def optimize_image(image_file, max_size_kb=settings.IMAGE_COMPRESS_MAX, format="WEBP", max_dimension=800):
4342
"""
4443
Optimize an image by resizing and converting it to the specified format while maintaining the aspect ratio.
4544
Ensures the image file size is below max_size_kb.
4645
"""
4746
img = PILImage.open(image_file)
48-
img = img.convert("RGB") # Ensure compatibility for formats like PNG with alpha
47+
img = img.convert("RGB") # Convert to RGB for compatibility
48+
49+
# Adjust dimension based on image type (smaller for PNG to ensure < 2023 bytes)
50+
if format.lower() == "png":
51+
max_dimension = 64 # Reduce size for PNG to keep it small
4952

50-
# Resize the image to fit within a reasonable dimension (adjust if needed)
51-
max_dimension = 800
5253
img.thumbnail((max_dimension, max_dimension), PILImage.Resampling.LANCZOS)
5354

54-
# Save the image to a buffer
55+
# Use a buffer to store the optimized image
5556
buffer = BytesIO()
56-
img.save(buffer, format=format, optimize=True, quality=85)
57-
buffer.seek(0)
5857

59-
# Check if the image exceeds the maximum size, reduce quality iteratively
60-
while buffer.tell() > max_size_kb * 1024:
61-
buffer.seek(0)
62-
img.save(buffer, format=format, optimize=True, quality=max(10, 85 - 10))
63-
buffer.seek(0)
58+
if format.lower() == "png":
59+
# Reduce colors using quantization (important for PNG)
60+
img = img.convert("P", palette=PILImage.ADAPTIVE, colors=32) # Limit colors to 32
61+
img.save(buffer, format="PNG", optimize=True)
62+
else:
63+
quality = 85
64+
img.save(buffer, format=format, optimize=True, quality=quality)
65+
66+
# Reduce quality iteratively for WebP/JPEG if size is too large
67+
while buffer.tell() > max_size_kb * 1024 and quality > 10:
68+
quality -= 5
69+
buffer = BytesIO() # Reset buffer
70+
img.save(buffer, format=format, optimize=True, quality=quality)
71+
72+
buffer.seek(0)
6473

6574
# Return the new image as a ContentFile
6675
return ContentFile(buffer.read(), name=f"{image_file.name.split('.')[0]}.{format.lower()}")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.11 on 2025-02-20 15:56
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("filemanager", "0002_initial"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="image",
15+
name="image_sm",
16+
field=models.ImageField(blank=True, null=True, upload_to=""),
17+
),
18+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.11 on 2025-02-20 16:00
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("filemanager", "0003_image_image_sm"),
10+
]
11+
12+
operations = [
13+
migrations.RenameField(
14+
model_name="image",
15+
old_name="image_sm",
16+
new_name="image_xs",
17+
),
18+
]

nxtbn/filemanager/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Image(AbstractBaseModel):
99
last_modified_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='image_modified', null=True, blank=True)
1010
name = models.CharField(max_length=255)
1111
image = models.ImageField()
12+
image_xs = models.ImageField(null=True, blank=True)
1213
image_alt_text = models.CharField(max_length=255)
1314

1415

nxtbn/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ def get_env_var(key, default=None, var_type=str):
404404
# ============================
405405
# NXTBN Specific Configuration
406406
# ============================
407+
IMAGE_COMPRESS_MAX = get_env_var("IMAGE_COMPRESS_MAX", default=200, var_type=int) # in KB
407408

408409
PLUGIN_BASE_DIR = 'nxtbn.plugins.sources'
409410

0 commit comments

Comments
 (0)