entry : connections.entrySet()) {
+ JInternalFrame camera = entry.getKey();
+ EffectsPanelFrame effects = entry.getValue();
+
+ if (camera.isVisible() && effects.isVisible() && !camera.isIcon() && !effects.isIcon()) {
+ drawBezierConnection(g2d, camera, effects);
+ }
+ }
+ g2d.dispose();
+ }
+
+ private void drawBezierConnection(Graphics2D g2d, JInternalFrame from, JInternalFrame to) {
+ Rectangle f = from.getBounds();
+ Rectangle t = to.getBounds();
+
+ int x1 = f.x + f.width;
+ int y1 = f.y + f.height / 2;
+ int x2 = t.x;
+ int y2 = t.y + t.height / 2;
+
+ int ctrlOffset = Math.min(Math.abs(x2 - x1) / 2, 150);
+ CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x1 + ctrlOffset, y1, x2 - ctrlOffset, y2, x2, y2);
+
+ g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+ g2d.setColor(new Color(100, 200, 255, 200));
+ g2d.draw(curve);
+
+ g2d.fillOval(x1 - 5, y1 - 5, 10, 10);
+ g2d.fillOval(x2 - 5, y2 - 5, 10, 10);
+ }
+ }
+
+ static class EffectsPanelFrame extends JInternalFrame {
+ public EffectsPanelFrame(String title, CameraPanel cameraPanel, JInternalFrame cameraFrame) {
+ super(title, true, true, true, true);
+ add(new FilterPanel(cameraPanel));
+
+ // Repaint curves when moving or iconifying
+ addComponentListener(new java.awt.event.ComponentAdapter() {
+ @Override public void componentMoved(java.awt.event.ComponentEvent e) {
+ if(getDesktopPane() != null) getDesktopPane().repaint();
+ }
+ });
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java b/src/main/java/io/swtc/proccessing/AutoGainProcessor.java
index 7138dcd..473ba4c 100644
--- a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java
+++ b/src/main/java/io/swtc/proccessing/AutoGainProcessor.java
@@ -1,12 +1,21 @@
package io.swtc.proccessing;
-/*
-*
-* Soon to be deprecated!
-*
-* */
+/**
+ * Gray World Algorithm.
+ *
+ *
+ * This class is an implementation of the Gray World algorithm, an automatic
+ * white balancing method.
+ *
+ *
+ *
+ * See:
+ *
+ * Stanford explanation
+ *
+ *
+ */
-@Deprecated
public class AutoGainProcessor {
public float[] calculateAutoGains(int[] pixels) {
diff --git a/src/main/java/io/swtc/proccessing/CameraPanel.java b/src/main/java/io/swtc/proccessing/CameraPanel.java
index fda37a7..f0d5620 100644
--- a/src/main/java/io/swtc/proccessing/CameraPanel.java
+++ b/src/main/java/io/swtc/proccessing/CameraPanel.java
@@ -3,16 +3,16 @@ package io.swtc.proccessing;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
+import java.util.function.Function;
-/*
-*
-* Now here its getting rather interesting! this class processes some
-* important stuff!
-*
-* */
-
+/**
+ * Enhanced CameraPanel with support for custom image processors
+ */
public class CameraPanel extends JPanel {
private BufferedImage currentImage;
+ private BufferedImage processedImage;
+
+ // Basic transform effects
private boolean mirror = false;
private boolean flip = false;
private boolean rotate = false;
@@ -21,150 +21,100 @@ public class CameraPanel extends JPanel {
private int brightness = 0;
private float contrast = 1.0f;
+ // Custom processor for advanced effects
+ private Function imageProcessor;
+
+ public CameraPanel() {
+ setBackground(Color.BLACK);
+ setPreferredSize(new Dimension(640, 480));
+ }
+
public void setImage(BufferedImage img) {
this.currentImage = img;
- this.repaint();
+ processImage();
+ repaint();
}
- public BufferedImage getCurrentImage() {
- return currentImage;
+ public void setImageProcessor(Function processor) {
+ this.imageProcessor = processor;
+ processImage();
+ repaint();
}
- public BufferedImage getCurrentProcessedImage() {
+ private void processImage() {
if (currentImage == null) {
- return null;
+ processedImage = null;
+ return;
}
- BufferedImage processed = currentImage;
+ BufferedImage result = currentImage;
- // apply color effects
- if (grayscale || invert || brightness != 0 || contrast != 1.0f) {
- processed = applyColorEffects(processed);
+ // Apply basic transforms first
+ result = applyBasicEffects(result);
+
+ // Apply custom processor if set
+ if (imageProcessor != null) {
+ result = imageProcessor.apply(result);
}
- // apply transform.
- if (mirror || flip || rotate) {
- processed = applyTransforms(processed);
- }
-
- return processed;
+ processedImage = result;
}
- /* Helper Methods ... its the same boilerplate shit over and over again, im sick of this. */
- public void setMirror(boolean mirror) {
- this.mirror = mirror;
- this.repaint();
- }
+ private BufferedImage applyBasicEffects(BufferedImage img) {
+ int width = img.getWidth();
+ int height = img.getHeight();
+ BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- public void setFlip(boolean flip) {
- this.flip = flip;
- this.repaint();
- }
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ // Handle mirror/flip/rotate
+ int srcX = x;
+ int srcY = y;
- public void setRotate(boolean rotate) {
- this.rotate = rotate;
- this.repaint();
- }
+ if (mirror) srcX = width - 1 - x;
+ if (flip) srcY = height - 1 - y;
+ if (rotate) {
+ int temp = srcX;
+ srcX = width - 1 - srcY;
+ srcY = temp;
+ }
- public void setGrayscale(boolean grayscale) {
- this.grayscale = grayscale;
- this.repaint();
- }
+ // Ensure coordinates are in bounds
+ srcX = Math.max(0, Math.min(width - 1, srcX));
+ srcY = Math.max(0, Math.min(height - 1, srcY));
- public void setInvert(boolean invert) {
- this.invert = invert;
- this.repaint();
- }
-
- public void setBrightness(int brightness) {
- this.brightness = brightness;
- this.repaint();
- }
-
- public void setContrast(float contrast) {
- this.contrast = contrast;
- this.repaint();
- }
-
- public void resetEffects() {
- mirror = flip = rotate = grayscale = invert = false;
- brightness = 0;
- contrast = 1.0f;
- this.repaint();
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
- if (currentImage != null) {
- Graphics2D g2 = (Graphics2D) g;
- g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-
- BufferedImage processedImage = currentImage;
-
- // effects
- if (grayscale || invert || brightness != 0 || contrast != 1.0f) {
- processedImage = applyColorEffects(processedImage);
- }
-
- // transforms
- int w = getWidth(), h = getHeight();
-
- if (rotate) {
- g2.translate(w / 2, h / 2);
- g2.rotate(Math.PI);
- g2.translate(-w / 2, -h / 2);
- }
-
-
- // here we have if, cause we need to do the stuff, yk?
- if (mirror && flip) {
- g2.drawImage(processedImage, w, h, -w, -h, null);
- } else if (mirror) {
- g2.drawImage(processedImage, w, 0, -w, h, null);
- } else if (flip) {
- g2.drawImage(processedImage, 0, h, w, -h, null);
- } else {
- g2.drawImage(processedImage, 0, 0, w, h, null);
- }
- }
- }
-
- private BufferedImage applyColorEffects(BufferedImage img) {
- BufferedImage result = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
-
- for (int y = 0; y < img.getHeight(); y++) {
- for (int x = 0; x < img.getWidth(); x++) {
- int rgb = img.getRGB(x, y);
+ int rgb = img.getRGB(srcX, srcY);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
+ // Apply grayscale
if (grayscale) {
int gray = (r + g + b) / 3;
r = g = b = gray;
}
- // this is fun, this regulates the brightness or the contrast!
- // this is some real java, the other stuff is just UI design, which i am bad at,
- // but this! This is some real shit
- r = (int) ((r - 128) * contrast + 128 + brightness);
- g = (int) ((g - 128) * contrast + 128 + brightness);
- b = (int) ((b - 128) * contrast + 128 + brightness);
+ // Apply brightness
+ if (brightness != 0) {
+ r = clamp(r + brightness);
+ g = clamp(g + brightness);
+ b = clamp(b + brightness);
+ }
- // invert the colors!
+ // Apply contrast
+ if (contrast != 1.0f) {
+ r = clamp((int)((r - 128) * contrast + 128));
+ g = clamp((int)((g - 128) * contrast + 128));
+ b = clamp((int)((b - 128) * contrast + 128));
+ }
+
+ // Apply invert
if (invert) {
r = 255 - r;
g = 255 - g;
b = 255 - b;
}
- // clamp so we dont get into weird color grades, or any weird looking spaces
- // if we dont do this we cant really do stuff which looks bearable
- r = Math.max(0, Math.min(255, r));
- g = Math.max(0, Math.min(255, g));
- b = Math.max(0, Math.min(255, b));
-
result.setRGB(x, y, (r << 16) | (g << 8) | b);
}
}
@@ -172,33 +122,95 @@ public class CameraPanel extends JPanel {
return result;
}
- private BufferedImage applyTransforms(BufferedImage img) {
- int width = img.getWidth();
- int height = img.getHeight();
- BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ private int clamp(int value) {
+ return Math.max(0, Math.min(255, value));
+ }
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- int sourceX = x;
- int sourceY = y;
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
- if (mirror) {
- sourceX = width - 1 - x;
- }
- if (flip) {
- sourceY = height - 1 - y;
- }
- if (rotate) {
- int tempX = width - 1 - sourceX;
- int tempY = height - 1 - sourceY;
- sourceX = tempX;
- sourceY = tempY;
- }
+ if (processedImage != null) {
+ Graphics2D g2d = (Graphics2D) g;
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- result.setRGB(x, y, img.getRGB(sourceX, sourceY));
- }
+ // Scale image to fit panel while maintaining aspect ratio
+ int panelWidth = getWidth();
+ int panelHeight = getHeight();
+ int imgWidth = processedImage.getWidth();
+ int imgHeight = processedImage.getHeight();
+
+ double scaleX = (double) panelWidth / imgWidth;
+ double scaleY = (double) panelHeight / imgHeight;
+ double scale = Math.min(scaleX, scaleY);
+
+ int scaledWidth = (int) (imgWidth * scale);
+ int scaledHeight = (int) (imgHeight * scale);
+
+ int x = (panelWidth - scaledWidth) / 2;
+ int y = (panelHeight - scaledHeight) / 2;
+
+ g2d.drawImage(processedImage, x, y, scaledWidth, scaledHeight, null);
}
+ }
- return result;
+ public BufferedImage getCurrentProcessedImage() {
+ return processedImage;
+ }
+
+ // Basic effect setters
+ public void setMirror(boolean mirror) {
+ this.mirror = mirror;
+ processImage();
+ repaint();
+ }
+
+ public void setFlip(boolean flip) {
+ this.flip = flip;
+ processImage();
+ repaint();
+ }
+
+ public void setRotate(boolean rotate) {
+ this.rotate = rotate;
+ processImage();
+ repaint();
+ }
+
+ public void setGrayscale(boolean grayscale) {
+ this.grayscale = grayscale;
+ processImage();
+ repaint();
+ }
+
+ public void setInvert(boolean invert) {
+ this.invert = invert;
+ processImage();
+ repaint();
+ }
+
+ public void setBrightness(int brightness) {
+ this.brightness = brightness;
+ processImage();
+ repaint();
+ }
+
+ public void setContrast(float contrast) {
+ this.contrast = contrast;
+ processImage();
+ repaint();
+ }
+
+ public void resetEffects() {
+ this.mirror = false;
+ this.flip = false;
+ this.rotate = false;
+ this.grayscale = false;
+ this.invert = false;
+ this.brightness = 0;
+ this.contrast = 1.0f;
+ this.imageProcessor = null;
+ processImage();
+ repaint();
}
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ConnectionOverlay.java b/src/main/java/io/swtc/proccessing/ConnectionOverlay.java
new file mode 100644
index 0000000..9be93fc
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ConnectionOverlay.java
@@ -0,0 +1,81 @@
+package io.swtc.proccessing;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.CubicCurve2D;
+
+/**
+ * Acts as a transparent layer over the entire window to draw connections.
+ */
+public class ConnectionOverlay extends JComponent {
+ private final Component source;
+ private final Component target;
+ private final Color connectionColor;
+
+ public ConnectionOverlay(Component source, Component target, Color color) {
+ this.source = source;
+ this.target = target;
+ this.connectionColor = color;
+ setOpaque(false); // Make sure we can see through it
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ if (!source.isShowing() || !target.isShowing()) return;
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // 1. Get absolute positions relative to this Overlay
+ Point p1 = SwingUtilities.convertPoint(source, 0, 0, this);
+ Point p2 = SwingUtilities.convertPoint(target, 0, 0, this);
+
+ int x1, y1, x2, y2, ctrl1X, ctrl2X;
+
+ // Calculate vertical centers
+ y1 = p1.y + (source.getHeight() / 2);
+ y2 = p2.y + (target.getHeight() / 2);
+
+ // 2. Logic to determine Left/Right orientation
+ // If source is to the left of target
+ if (p1.x + source.getWidth() < p2.x) {
+ x1 = p1.x + source.getWidth(); // Right edge of source
+ x2 = p2.x; // Left edge of target
+ }
+ // If source is to the right of target
+ else if (p1.x > p2.x + target.getWidth()) {
+ x1 = p1.x; // Left edge of source
+ x2 = p2.x + target.getWidth(); // Right edge of target
+ }
+ // If they are overlapping horizontally, use centers
+ else {
+ x1 = p1.x + (source.getWidth() / 2);
+ x2 = p2.x + (target.getWidth() / 2);
+ }
+
+ // 3. Dynamic Curve "Stiffness"
+ // The horizontal distance between the two points determines how far the curve pulls
+ int horizontalDist = Math.abs(x1 - x2);
+ int offset = Math.max(horizontalDist / 2, 20); // Minimum 20px pull for short distances
+
+ if (x1 < x2) {
+ ctrl1X = x1 + offset;
+ ctrl2X = x2 - offset;
+ } else {
+ ctrl1X = x1 - offset;
+ ctrl2X = x2 + offset;
+ }
+
+ // 4. Draw the Curve
+ g2d.setColor(connectionColor);
+ g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+
+ // We keep the Y coordinates for controls the same as the endpoints
+ // to create that "horizontal entry/exit" look.
+ CubicCurve2D curve = new CubicCurve2D.Float(x1, y1, ctrl1X, y1, ctrl2X, y2, x2, y2);
+ g2d.draw(curve);
+
+ g2d.dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/EffectState.java b/src/main/java/io/swtc/proccessing/EffectState.java
new file mode 100644
index 0000000..c0d45ce
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/EffectState.java
@@ -0,0 +1,25 @@
+package io.swtc.proccessing;
+
+public class EffectState {
+ public final boolean awbEnabled, dnrEnabled, edgeEnhance;
+ public final int awbStrength, dnrSpatial, dnrTemporal;
+ public final int temperature, tint, saturation, shadows, highlights, sharpness;
+
+ public EffectState(boolean awbEnabled, int awbStrength, boolean dnrEnabled,
+ int dnrSpatial, int dnrTemporal, int temperature, int tint,
+ int saturation, int shadows, int highlights,
+ int sharpness, boolean edgeEnhance) {
+ this.awbEnabled = awbEnabled;
+ this.awbStrength = awbStrength;
+ this.dnrEnabled = dnrEnabled;
+ this.dnrSpatial = dnrSpatial;
+ this.dnrTemporal = dnrTemporal;
+ this.temperature = temperature;
+ this.tint = tint;
+ this.saturation = saturation;
+ this.shadows = shadows;
+ this.highlights = highlights;
+ this.sharpness = sharpness;
+ this.edgeEnhance = edgeEnhance;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/FilterPanel.java b/src/main/java/io/swtc/proccessing/FilterPanel.java
new file mode 100644
index 0000000..2258e42
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/FilterPanel.java
@@ -0,0 +1,273 @@
+package io.swtc.proccessing;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.image.BufferedImage;
+import java.util.Random;
+
+public class FilterPanel extends JPanel {
+
+ private final CameraPanel cameraPanel;
+ private final AutoGainProcessor autoGainProcessor;
+ private final PresetLibrary presetLibrary;
+ private final Color connectionColor;
+
+ private JCheckBox awbEnabled, dnrEnabled, edgeEnhance;
+ private JSlider awbStrength, dnrStrength, dnrTemporal, sharpness;
+ private JSlider saturation, temperature, tint, shadows, highlights;
+ private JComboBox presetCombo;
+
+ private float[] currentGains = {1f, 1f, 1f};
+
+ public FilterPanel(CameraPanel cameraPanel) {
+ this.cameraPanel = cameraPanel;
+ this.autoGainProcessor = new AutoGainProcessor();
+ this.presetLibrary = new PresetLibrary();
+
+ // FIX: Randomize color with high saturation so it stands out
+ Random rand = new Random();
+ this.connectionColor = Color.getHSBColor(rand.nextFloat(), 0.9f, 0.9f);
+
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ buildUI();
+ initListeners(); // Initialize anti-ghosting listeners
+ }
+
+
+ private void buildUI() {
+ add(createPresetPanel());
+ add(Box.createRigidArea(new Dimension(0, 10)));
+
+ JPanel container = new JPanel();
+ container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
+
+ container.add(createAWBPanel());
+ container.add(Box.createRigidArea(new Dimension(0, 10)));
+ container.add(createDNRPanel());
+ container.add(Box.createRigidArea(new Dimension(0, 10)));
+ container.add(createColorPanel());
+ container.add(Box.createRigidArea(new Dimension(0, 10)));
+ container.add(createDetailPanel());
+
+ JScrollPane scroll = new JScrollPane(container);
+ scroll.setBorder(null);
+ scroll.getVerticalScrollBar().setUnitIncrement(16);
+ add(scroll);
+
+ add(Box.createRigidArea(new Dimension(0, 10)));
+ add(createActionPanel());
+ }
+
+ public EffectState getCurrentState() {
+ return new EffectState(
+ awbEnabled.isSelected(), awbStrength.getValue(),
+ dnrEnabled.isSelected(), dnrStrength.getValue(), dnrTemporal.getValue(),
+ temperature.getValue(), tint.getValue(), saturation.getValue(),
+ shadows.getValue(), highlights.getValue(),
+ sharpness.getValue(), edgeEnhance.isSelected()
+ );
+ }
+
+ // Inside AdvancedEffectsPanel.java
+
+ private void initListeners() {
+ ComponentAdapter repaintListener = new ComponentAdapter() {
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ updateOverlay();
+ }
+ @Override
+ public void componentResized(ComponentEvent e) {
+ updateOverlay();
+ }
+ };
+
+ this.addComponentListener(repaintListener);
+ if (this.cameraPanel != null) {
+ this.cameraPanel.addComponentListener(repaintListener);
+ }
+ }
+
+ private void updateOverlay() {
+ RootPaneContainer root = (RootPaneContainer) SwingUtilities.getWindowAncestor(this);
+ if (root != null) {
+ root.getGlassPane().repaint();
+ }
+ }
+
+ private void applyToCamera() {
+ EffectState state = getCurrentState();
+ cameraPanel.setImageProcessor(img ->
+ ImageEffectEngine.applyEffects(img, state, currentGains)
+ );
+ }
+
+ private JPanel createPresetPanel() {
+ JPanel panel = new JPanel(new BorderLayout(5, 5));
+ panel.setBorder(createTitledBorder("Presets"));
+
+ presetCombo = new JComboBox<>(new String[]{"Custom", "Natural", "Vivid", "Portrait", "Low Light", "Cinematic"});
+ JButton saveBtn = new JButton("Save");
+
+ presetCombo.addActionListener(e -> {
+ String selected = (String) presetCombo.getSelectedItem();
+ EffectState state = presetLibrary.get(selected);
+ if (state != null) applyPresetToUI(state);
+ });
+
+ saveBtn.addActionListener(e -> {
+ String name = JOptionPane.showInputDialog(this, "Preset Name:");
+ if (name != null) {
+ presetLibrary.savePreset(name, getCurrentState());
+ presetCombo.addItem(name);
+ }
+ });
+
+ panel.add(presetCombo, BorderLayout.CENTER);
+ panel.add(saveBtn, BorderLayout.EAST);
+ return panel;
+ }
+
+ private JPanel createDNRPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(createTitledBorder("3D Denoise (DNR)"));
+
+ dnrEnabled = new JCheckBox("Enable Temporal Denoise");
+ dnrStrength = addSliderToPanel(panel, "Spatial Strength", 0, 100, 30, "%");
+ dnrTemporal = addSliderToPanel(panel, "Temporal Strength", 0, 100, 50, "%");
+
+ dnrEnabled.addActionListener(e -> {
+ boolean enabled = dnrEnabled.isSelected();
+ dnrStrength.setEnabled(enabled);
+ dnrTemporal.setEnabled(enabled);
+ applyToCamera();
+ });
+
+ dnrStrength.setEnabled(false);
+ dnrTemporal.setEnabled(false);
+
+ panel.add(dnrEnabled, 0);
+ return panel;
+ }
+
+ private JPanel createDetailPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(createTitledBorder("Detail & Sharpness"));
+
+ sharpness = addSliderToPanel(panel, "Sharpness", 0, 200, 100, "%");
+ edgeEnhance = new JCheckBox("Edge Enhancement");
+
+ edgeEnhance.addActionListener(e -> applyToCamera());
+
+ panel.add(edgeEnhance);
+ return panel;
+ }
+
+ private JPanel createAWBPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(createTitledBorder("White Balance"));
+
+ awbEnabled = new JCheckBox("Enable AWB");
+ JPanel sPanel = createSliderPanel("Strength", 0, 100, 100, "%");
+ awbStrength = (JSlider) sPanel.getComponent(1);
+ JButton balanceBtn = new JButton("Balance Now");
+
+ awbEnabled.addActionListener(e -> {
+ boolean active = awbEnabled.isSelected();
+ awbStrength.setEnabled(active);
+ balanceBtn.setEnabled(active);
+ if (active) performOneTimeBalance();
+ else { currentGains = new float[]{1f, 1f, 1f}; applyToCamera(); }
+ });
+
+ awbStrength.addChangeListener(e -> { if(!awbStrength.getValueIsAdjusting()) applyToCamera(); });
+ balanceBtn.addActionListener(e -> performOneTimeBalance());
+
+ panel.add(awbEnabled);
+ panel.add(sPanel);
+ panel.add(balanceBtn);
+ return panel;
+ }
+
+ private JPanel createColorPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(createTitledBorder("Color Grading"));
+
+ temperature = addSliderToPanel(panel, "Temperature", -100, 100, 0, "");
+ tint = addSliderToPanel(panel, "Tint", -100, 100, 0, "");
+ saturation = addSliderToPanel(panel, "Saturation", 0, 200, 100, "%");
+ shadows = addSliderToPanel(panel, "Shadows", -100, 100, 0, "");
+ highlights = addSliderToPanel(panel, "Highlights", -100, 100, 0, "");
+
+ return panel;
+ }
+
+ private void performOneTimeBalance() {
+ BufferedImage img = cameraPanel.getCurrentProcessedImage();
+ if (img != null) {
+ int[] pixels = img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
+ currentGains = autoGainProcessor.calculateAutoGains(pixels);
+ applyToCamera();
+ }
+ }
+
+ private void applyPresetToUI(EffectState s) {
+ awbEnabled.setSelected(s.awbEnabled);
+ awbStrength.setValue(s.awbStrength);
+ dnrEnabled.setSelected(s.dnrEnabled);
+ dnrStrength.setValue(s.dnrSpatial);
+ temperature.setValue(s.temperature);
+ tint.setValue(s.tint);
+ saturation.setValue(s.saturation);
+ shadows.setValue(s.shadows);
+ highlights.setValue(s.highlights);
+ sharpness.setValue(s.sharpness);
+ edgeEnhance.setSelected(s.edgeEnhance);
+ applyToCamera();
+ }
+
+ private JSlider addSliderToPanel(JPanel parent, String label, int min, int max, int val, String unit) {
+ JPanel p = createSliderPanel(label, min, max, val, unit);
+ JSlider s = (JSlider) p.getComponent(1);
+ s.addChangeListener(e -> { if(!s.getValueIsAdjusting()) applyToCamera(); });
+ parent.add(p);
+ parent.add(Box.createRigidArea(new Dimension(0, 5)));
+ return s;
+ }
+
+ private JPanel createSliderPanel(String label, int min, int max, int initial, String unit) {
+ JPanel panel = new JPanel(new BorderLayout());
+ JLabel title = new JLabel(label + ": " + initial + unit);
+ JSlider slider = new JSlider(min, max, initial);
+ slider.addChangeListener(e -> title.setText(label + ": " + slider.getValue() + unit));
+ panel.add(title, BorderLayout.NORTH);
+ panel.add(slider, BorderLayout.CENTER);
+ return panel;
+ }
+
+ private TitledBorder createTitledBorder(String title) {
+ return BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), title);
+ }
+
+ private JPanel createActionPanel() {
+ JPanel panel = new JPanel(new GridLayout(1, 2, 10, 0));
+ JButton reset = new JButton("Reset All");
+ reset.addActionListener(e -> resetUI());
+ panel.add(reset);
+ return panel;
+ }
+
+ private void resetUI() {
+ applyPresetToUI(new EffectState(false, 100, false, 30, 50, 0, 0, 100, 0, 0, 100, false));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ImageEffectEngine.java b/src/main/java/io/swtc/proccessing/ImageEffectEngine.java
new file mode 100644
index 0000000..65eb22f
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ImageEffectEngine.java
@@ -0,0 +1,147 @@
+package io.swtc.proccessing;
+
+import java.awt.image.BufferedImage;
+
+public class ImageEffectEngine {
+
+ public static BufferedImage applyEffects(BufferedImage img, EffectState state, float[] currentGains) {
+ if (img == null) return null;
+
+ int width = img.getWidth();
+ int height = img.getHeight();
+ BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int rgb = img.getRGB(x, y);
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+
+ // 1. AWB
+ if (state.awbEnabled) {
+ float s = state.awbStrength / 100f;
+ r = (int) Math.min(255, r * (1 + (currentGains[0] - 1) * s));
+ g = (int) Math.min(255, g * (1 + (currentGains[1] - 1) * s));
+ b = (int) Math.min(255, b * (1 + (currentGains[2] - 1) * s));
+ }
+
+ // 2. Temp & Tint
+ if (state.temperature != 0) {
+ float factor = state.temperature / 100f;
+ r = clamp(r + (int)(factor * 30));
+ b = clamp(b - (int)(factor * 30));
+ }
+ if (state.tint != 0) {
+ g = clamp(g + (int)((state.tint / 100f) * 20));
+ }
+
+ // 3. Saturation
+ if (state.saturation != 100) {
+ float factor = state.saturation / 100f;
+ float gray = (r + g + b) / 3f;
+ r = clamp((int)(gray + (r - gray) * factor));
+ g = clamp((int)(gray + (g - gray) * factor));
+ b = clamp((int)(gray + (b - gray) * factor));
+ }
+
+ // 4. Shadows/Highlights
+ float lum = (r + g + b) / 3f / 255f;
+ if (lum < 0.5f && state.shadows != 0) {
+ int adj = (int)((state.shadows / 100f) * (1 - lum * 2) * 50);
+ r = clamp(r + adj); g = clamp(g + adj); b = clamp(b + adj);
+ } else if (lum > 0.5f && state.highlights != 0) {
+ int adj = (int)((state.highlights / 100f) * (lum * 2 - 1) * 50);
+ r = clamp(r + adj); g = clamp(g + adj); b = clamp(b + adj);
+ }
+
+ result.setRGB(x, y, (r << 16) | (g << 8) | b);
+ }
+ }
+
+ if (state.sharpness != 100 || state.edgeEnhance) {
+ result = applySharpness(result, state.sharpness / 100f, state.edgeEnhance);
+ }
+
+ if (state.dnrEnabled) {
+ result = applyDenoise(result, state.dnrSpatial / 100f);
+ }
+
+ return result;
+ }
+
+ private static BufferedImage applySharpness(BufferedImage img, float amount, boolean edgeEnhance) {
+ if (amount == 1f && !edgeEnhance) return img;
+
+ int width = img.getWidth();
+ int height = img.getHeight();
+ BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ float[][] kernel = edgeEnhance ?
+ new float[][]{{0, -1, 0}, {-1, 5, -1}, {0, -1, 0}} :
+ new float[][]{{-1, -1, -1}, {-1, 9, -1}, {-1, -1, -1}};
+
+ for (int y = 1; y < height - 1; y++) {
+ for (int x = 1; x < width - 1; x++) {
+ float r = 0, g = 0, b = 0;
+
+ for (int ky = -1; ky <= 1; ky++) {
+ for (int kx = -1; kx <= 1; kx++) {
+ int rgb = img.getRGB(x + kx, y + ky);
+ float weight = kernel[ky + 1][kx + 1] * (amount - 1) / 8f;
+ if (kx == 0 && ky == 0) weight += 1;
+
+ r += ((rgb >> 16) & 0xFF) * weight;
+ g += ((rgb >> 8) & 0xFF) * weight;
+ b += (rgb & 0xFF) * weight;
+ }
+ }
+
+ result.setRGB(x, y, (clamp((int)r) << 16) | (clamp((int)g) << 8) | clamp((int)b));
+ }
+ }
+
+ return result;
+ }
+
+ private static BufferedImage applyDenoise(BufferedImage img, float strength) {
+ if (strength == 0) return img;
+
+ int width = img.getWidth();
+ int height = img.getHeight();
+ BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ int radius = (int)(strength * 2) + 1;
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int rSum = 0, gSum = 0, bSum = 0, count = 0;
+
+ for (int dy = -radius; dy <= radius; dy++) {
+ for (int dx = -radius; dx <= radius; dx++) {
+ int nx = Math.min(width - 1, Math.max(0, x + dx));
+ int ny = Math.min(height - 1, Math.max(0, y + dy));
+
+ int rgb = img.getRGB(nx, ny);
+ rSum += (rgb >> 16) & 0xFF;
+ gSum += (rgb >> 8) & 0xFF;
+ bSum += rgb & 0xFF;
+ count++;
+ }
+ }
+
+ int r = rSum / count;
+ int g = gSum / count;
+ int b = bSum / count;
+
+ result.setRGB(x, y, (r << 16) | (g << 8) | b);
+ }
+ }
+
+ return result;
+ }
+
+ private static int clamp(int val) {
+ return Math.max(0, Math.min(255, val));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/PresetLibrary.java b/src/main/java/io/swtc/proccessing/PresetLibrary.java
new file mode 100644
index 0000000..56f81ca
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/PresetLibrary.java
@@ -0,0 +1,46 @@
+package io.swtc.proccessing;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PresetLibrary {
+ private final Map presets = new HashMap<>();
+
+ public PresetLibrary() {
+ presets.put("Natural", new EffectState(
+ false, 100, false, 20, 40, 0, 0, 100, 0, 0, 100, false
+ ));
+
+ presets.put("Vivid", new EffectState(
+ true, 100, false, 15, 30, 10, 5, 130, 0, 0, 120, true
+ ));
+
+ presets.put("Portrait", new EffectState(
+ true, 80, true, 40, 50, -5, 10, 95, 10, -5, 90, false
+ ));
+
+ presets.put("Low Light", new EffectState(
+ true, 100, true, 60, 70, 0, 0, 110, 20, -10, 80, false
+ ));
+
+ presets.put("High Contrast", new EffectState(
+ false, 100, false, 25, 35, 0, 0, 120, -20, 20, 130, true
+ ));
+
+ presets.put("Cinematic", new EffectState(
+ true, 70, true, 30, 45, -15, -5, 90, -10, 5, 110, false
+ ));
+ }
+
+ public void savePreset(String name, EffectState state) {
+ presets.put(name, state);
+ }
+
+ public EffectState get(String name) {
+ return presets.get(name);
+ }
+
+ public String[] getPresetNames() {
+ return presets.keySet().toArray(new String[0]);
+ }
+}
\ No newline at end of file