Image desqueeze

by James Doyle

HTML

<div class="container">
  <header>
    <h1>Image Width Stretch Tool</h1>
    <p class="subtitle">
      Upload an image and adjust the width slider to stretch it horizontally.
      Click on the output image to download the edited version.
    </p>
  </header>

  <div class="upload-container">
    <div class="upload-icon">📁</div>
    <p>Upload an image to get started</p>
    <input type="file" id="fileInput" class="file-input" accept="image/*" />
    <button class="upload-btn" id="uploadBtn">Choose Image</button>
  </div>

  <div class="controls" id="controls">
    <label for="stretchSlider"
      >Stretch Factor: <span id="factorValue">1.00</span>x</label
    >
    <div class="slider-container">
      <input
        type="range"
        min="1"
        max="2"
        step="0.01"
        value="1"
        class="slider"
        id="stretchSlider"
      />
    </div>
    <div class="value-display">
      Width: <span id="widthValue">100%</span> of original
    </div>
  </div>

  <div class="canvas-container" id="canvasContainer">
    <canvas id="imageCanvas"></canvas>
    <div class="download-hint">Click on the image to download</div>
  </div>

  <div class="instructions">
    <h2>How to Use</h2>
    <ol>
      <li>Click "Choose Image" to upload any image from your device</li>
      <li>
        Use the slider to adjust the horizontal stretch factor (1.00x to 2.00x)
      </li>
      <li>Watch the image stretch in real-time as you move the slider</li>
      <li>Click on the output image to download the edited version</li>
      <li>
        All processing happens in your browser - your image never leaves your
        computer
      </li>
    </ol>
  </div>
</div>

<footer>
  <p>Client-Side Image Processing | No Server Required</p>
</footer>

CSS

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}

body {
  background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
  color: white;
  min-height: 100vh;
  padding: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.container {
  max-width: 900px;
  width: 100%;
  background: rgba(0, 0, 0, 0.7);
  border-radius: 15px;
  padding: 30px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  margin-top: 20px;
}

header {
  text-align: center;
  margin-bottom: 30px;
}

h1 {
  font-size: 2.5rem;
  margin-bottom: 10px;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}

.subtitle {
  font-size: 1.1rem;
  opacity: 0.9;
  max-width: 600px;
  margin: 0 auto;
}

.upload-container {
  background: rgba(255, 255, 255, 0.1);
  border: 2px dashed #4a90e2;
  border-radius: 10px;
  padding: 30px;
  text-align: center;
  margin-bottom: 30px;
  transition: all 0.3s ease;
}

.upload-container:hover {
  background: rgba(255, 255, 255, 0.15);
  border-color: #5cb85c;
}

.upload-icon {
  font-size: 3rem;
  margin-bottom: 15px;
  color: #4a90e2;
}

.file-input {
  display: none;
}

.upload-btn {
  background: #4a90e2;
  color: white;
  padding: 12px 25px;
  border-radius: 50px;
  font-size: 1.1rem;
  cursor: pointer;
  transition: all 0.3s ease;
  border: none;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
}

.upload-btn:hover {
  background: #357abd;
  transform: translateY(-2px);
  box-shadow: 0 6px 8px rgba(0, 0, 0, 0.3);
}

.controls {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  padding: 25px;
  margin-bottom: 30px;
  display: none;
}

.controls.active {
  display: block;
  animation: fadeIn 0.5s ease;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.slider-container...

JavaScript

const fileInput = document.getElementById("fileInput");
const uploadBtn = document.getElementById("uploadBtn");
const stretchSlider = document.getElementById("stretchSlider");
const factorValue = document.getElementById("factorValue");
const widthValue = document.getElementById("widthValue");
const controls = document.getElementById("controls");
const canvasContainer = document.getElementById("canvasContainer");
const canvas = document.getElementById("imageCanvas");
const ctx = canvas.getContext("2d");

let originalImage = null;
let currentStretchFactor = 1;

// Trigger file input when upload button is clicked
uploadBtn.addEventListener("click", () => {
  fileInput.click();
});

// Handle file selection
fileInput.addEventListener("change", function (e) {
  if (this.files && this.files[0]) {
    const reader = new FileReader();

    reader.onload = function (event) {
      originalImage = new Image();
      originalImage.onload = function () {
        // Show controls and canvas
        controls.classList.add("active");
        canvasContainer.classList.add("active");

        // Reset slider to default
        stretchSlider.value = 1;
        factorValue.textContent = "1.00";
        widthValue.textContent = "100%";
        currentStretchFactor = 1;

        // Draw the original image
        drawStretchedImage(1);
      };
      originalImage.src = event.target.result;
    };

    reader.readAsDataURL(this.files[0]);
  }
});

// Handle slider changes
stretchSlider.addEventListener("input", function () {
  const factor = parseFloat(this.value);
  currentStretchFactor = factor;
  factorValue.textContent = factor.toFixed(2);
  widthValue.textContent = Math.round(factor * 100) + "%";

  if (originalImage) {
    drawStretchedImage(factor);
  }
});

// Download image when canvas is clicked
canvas.addEventListener("click", function () {
  if (originalImage) {
    downloadImage();
  }
});

// Function to...