From d739e4086208ea48110141f5f5f250feaebafd72 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Thu, 4 Sep 2025 15:15:05 +0200 Subject: [PATCH] Add brightness and contrast controls --- croppa/main.py | 68 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/croppa/main.py b/croppa/main.py index 595c765..a0d10bc 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -83,6 +83,10 @@ class VideoEditor: # Rotation settings self.rotation_angle = 0 # 0, 90, 180, 270 degrees + # Brightness and contrast settings + self.brightness = 0 # -100 to 100 + self.contrast = 1.0 # 0.1 to 3.0 + # Cut points self.cut_start_frame = None self.cut_end_frame = None @@ -121,12 +125,14 @@ class VideoEditor: self.playback_speed = 1.0 self.current_display_frame = None - # Reset crop, zoom, rotation, and cut settings for new video + # Reset crop, zoom, rotation, brightness/contrast, and cut settings for new video self.crop_rect = None self.crop_history = [] self.zoom_factor = 1.0 self.zoom_center = None self.rotation_angle = 0 + self.brightness = 0 + self.contrast = 1.0 self.cut_start_frame = None self.cut_end_frame = None self.display_offset = [0, 0] @@ -198,21 +204,24 @@ class VideoEditor: return self.load_current_frame() def apply_crop_zoom_and_rotation(self, frame): - """Apply current crop, zoom, and rotation settings to frame""" + """Apply current crop, zoom, rotation, and brightness/contrast settings to frame""" if frame is None: return None processed_frame = frame.copy() - # Apply crop first + # Apply brightness/contrast first (to original frame for best quality) + processed_frame = self.apply_brightness_contrast(processed_frame) + + # Apply crop if self.crop_rect: x, y, w, h = self.crop_rect x, y, w, h = int(x), int(y), int(w), int(h) # Ensure crop is within frame bounds - x = max(0, min(x, frame.shape[1] - 1)) - y = max(0, min(y, frame.shape[0] - 1)) - w = min(w, frame.shape[1] - x) - h = min(h, frame.shape[0] - y) + x = max(0, min(x, processed_frame.shape[1] - 1)) + y = max(0, min(y, processed_frame.shape[0] - 1)) + w = min(w, processed_frame.shape[1] - x) + h = min(h, processed_frame.shape[0] - y) if w > 0 and h > 0: processed_frame = processed_frame[y:y+h, x:x+w] @@ -254,6 +263,26 @@ class VideoEditor: """Rotate video 90 degrees clockwise""" self.rotation_angle = (self.rotation_angle + 90) % 360 + def apply_brightness_contrast(self, frame): + """Apply brightness and contrast adjustments to frame""" + if self.brightness == 0 and self.contrast == 1.0: + return frame + + # Convert brightness from -100/100 range to -255/255 range + brightness_value = self.brightness * 2.55 + + # Apply brightness and contrast: new_pixel = contrast * old_pixel + brightness + adjusted = cv2.convertScaleAbs(frame, alpha=self.contrast, beta=brightness_value) + return adjusted + + def adjust_brightness(self, delta: int): + """Adjust brightness by delta (-100 to 100)""" + self.brightness = max(-100, min(100, self.brightness + delta)) + + def adjust_contrast(self, delta: float): + """Adjust contrast by delta (0.1 to 3.0)""" + self.contrast = max(0.1, min(3.0, self.contrast + delta)) + def draw_timeline(self, frame): """Draw timeline at the bottom of the frame""" height, width = frame.shape[:2] @@ -370,7 +399,9 @@ class VideoEditor: # Add info overlay rotation_text = f" | Rotation: {self.rotation_angle}°" if self.rotation_angle != 0 else "" - info_text = f"Frame: {self.current_frame}/{self.total_frames} | Speed: {self.playback_speed:.1f}x | Zoom: {self.zoom_factor:.1f}x{rotation_text} | {'Playing' if self.is_playing else 'Paused'}" + brightness_text = f" | Brightness: {self.brightness}" if self.brightness != 0 else "" + contrast_text = f" | Contrast: {self.contrast:.1f}" if self.contrast != 1.0 else "" + info_text = f"Frame: {self.current_frame}/{self.total_frames} | Speed: {self.playback_speed:.1f}x | Zoom: {self.zoom_factor:.1f}x{rotation_text}{brightness_text}{contrast_text} | {'Playing' if self.is_playing else 'Paused'}" cv2.putText(canvas, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) cv2.putText(canvas, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 1) @@ -648,6 +679,9 @@ class VideoEditor: else: return None + # Apply brightness and contrast + frame = self.apply_brightness_contrast(frame) + # Apply rotation if self.rotation_angle != 0: frame = self.apply_rotation(frame) @@ -682,6 +716,8 @@ class VideoEditor: print(" Shift+A/D: Seek backward/forward (10 frames)") print(" Ctrl+A/D: Seek backward/forward (60 frames)") print(" W/S: Increase/Decrease speed") + print(" E/Shift+E: Increase/Decrease brightness") + print(" R/Shift+R: Increase/Decrease contrast") print(" -: Rotate clockwise 90°") print(" Shift+Click+Drag: Select crop area") print(" U: Undo crop") @@ -740,6 +776,22 @@ class VideoEditor: self.playback_speed = min(self.MAX_PLAYBACK_SPEED, self.playback_speed + self.SPEED_INCREMENT) elif key == ord('s'): self.playback_speed = max(self.MIN_PLAYBACK_SPEED, self.playback_speed - self.SPEED_INCREMENT) + elif key == ord('e') or key == ord('E'): + # Brightness adjustment: E (increase), Shift+E (decrease) + if key == ord('E'): + self.adjust_brightness(-5) + print(f"Brightness: {self.brightness}") + else: + self.adjust_brightness(5) + print(f"Brightness: {self.brightness}") + elif key == ord('r') or key == ord('R'): + # Contrast adjustment: R (increase), Shift+R (decrease) + if key == ord('R'): + self.adjust_contrast(-0.1) + print(f"Contrast: {self.contrast:.1f}") + else: + self.adjust_contrast(0.1) + print(f"Contrast: {self.contrast:.1f}") elif key == ord('u'): self.undo_crop() elif key == ord('c'):