App-BlurFill

 view release on metacpan or  search on metacpan

lib/App/BlurFill/Web.pm  view on Meta::CPAN


The image file to be processed. This parameter is required.
It should be a valid image file format (e.g., JPEG, PNG, GIF).

=head2 width

The desired width of the output image. Default is 650 pixels.

=head2 height

The desired height of the output image. Default is 350 pixels.

=head1 EXAMPLE

  POST /blur
  Content-Type: multipart/form-data

  image: <binary image data>
  width: 800
  height: 600

=head2 Using C<curl>

  # This will return HTML with the results page
  curl -X POST -F "image=@path/to/image.jpg" -F "width=800" -F "height=600" http://localhost:3000/blur
  
  # To download the image directly
  curl -OJ http://localhost:3000/download/image_blur.png

=head1 RESPONSE

The POST /blur response will be an HTML page displaying the blurred image with
download options. The GET /download/:filename response will be the actual image file.

=cut

use v5.40;

package App::BlurFill::Web;
use Dancer2;

our $VERSION = '0.0.5';

use File::Temp qw(tempfile tempdir);
use File::Spec;
use File::Copy;
use App::BlurFill;

# Create a persistent temp directory for storing processed images
my $TEMP_DIR = File::Temp::tempdir(CLEANUP => 1);

sub _get_css {
  return <<'CSS';
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 20px;
    }
    
    .container {
      background: white;
      border-radius: 16px;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
      max-width: 600px;
      width: 100%;
      padding: 40px;
    }
    
    h1 {
      color: #333;
      font-size: 32px;
      font-weight: 700;
      margin-bottom: 12px;
      text-align: center;
    }
    
    .subtitle {
      color: #666;
      font-size: 16px;
      text-align: center;
      margin-bottom: 32px;
      line-height: 1.5;
    }
    
    .form-group {
      margin-bottom: 24px;
    }
    
    label {
      display: block;
      color: #444;
      font-weight: 600;
      margin-bottom: 8px;
      font-size: 14px;
    }
    
    input[type="file"] {
      display: block;
      width: 100%;
      padding: 12px;
      border: 2px dashed #667eea;
      border-radius: 8px;
      background: #f8f9ff;
      cursor: pointer;
      transition: all 0.3s ease;
    }
    
    input[type="file"]:hover {
      border-color: #764ba2;
      background: #f0f1ff;
    }
    
    input[type="number"] {
      width: 100%;
      padding: 12px 16px;
      border: 2px solid #e0e0e0;
      border-radius: 8px;
      font-size: 16px;
      transition: all 0.3s ease;
    }
    
    input[type="number"]:focus {
      outline: none;
      border-color: #667eea;
      box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
    }
    
    .dimensions {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 16px;
    }
    
    button, .button {
      width: 100%;
      padding: 16px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      border-radius: 8px;
      font-size: 18px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s ease;
      box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
      text-decoration: none;
      display: inline-block;
      text-align: center;
    }
    
    button:hover, .button:hover {
      transform: translateY(-2px);
      box-shadow: 0 6px 16px rgba(102, 126, 234, 0.5);
    }
    
    button:active, .button:active {
      transform: translateY(0);
    }
    
    .info {
      background: #f8f9ff;
      border-left: 4px solid #667eea;
      padding: 16px;
      margin-top: 24px;
      border-radius: 4px;
    }
    
    .info p {
      color: #555;
      font-size: 14px;
      line-height: 1.6;
      margin-bottom: 8px;
    }
    
    .info p:last-child {
      margin-bottom: 0;
    }
    
    .info strong {
      color: #333;
    }

    .credits {
      padding-top: 1em;
      color: #999;
      text-align: center;
    }

    .credits a:link {
      color: #c9c;
    }

    .credits a:visited {
      color: #969;
    }

    .result-image {
      margin: 24px 0;
      text-align: center;
      border-radius: 8px;
      overflow: hidden;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }

    .result-image img {
      max-width: 100%;
      height: auto;
      display: block;
    }

    .success-message {
      background: #f0fdf4;
      border-left: 4px solid #10b981;
      padding: 16px;
      margin-bottom: 24px;
      border-radius: 4px;
      color: #065f46;
    }

    .action-buttons {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 16px;
      margin-top: 24px;
    }

    .button-secondary {
      background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
    }
    
    @media (max-width: 640px) {
      .container {
        padding: 24px;
      }
      
      h1 {
        font-size: 24px;
      }
      
      .dimensions, .action-buttons {
        grid-template-columns: 1fr;
      }
    }
CSS
}

get '/' => sub {
  my $css = _get_css();
  return <<"HTML";
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>BlurFill - Perfect crops, zero letterboxing: smart blur-fill from your source image.</title>
  <style>
$css
  </style>
</head>
<body>
  <div class="container">
    <h1>BlurFill</h1>
    <p class="subtitle">Perfect crops, zero letterboxing: smart blur-fill from your source image.</p>
    
    <form action="/blur" method="POST" enctype="multipart/form-data">
      <div class="form-group">
        <label for="image">Select Image</label>
        <input type="file" id="image" name="image" accept="image/jpeg,image/jpg,image/png,image/gif" required>
      </div>
      
      <div class="form-group">
        <label>Output Dimensions</label>
        <div class="dimensions">
          <div>
            <label for="width">Width (px)</label>
            <input type="number" id="width" name="width" value="650" min="1" max="4000">
          </div>
          <div>
            <label for="height">Height (px)</label>
            <input type="number" id="height" name="height" value="350" min="1" max="4000">
          </div>
        </div>
      </div>
      
      <button type="submit">Generate resized image</button>
    </form>
    
    <div class="info">
      <p><strong>How it works:</strong></p>
      <p>1. Upload your image (JPEG, PNG, or GIF)</p>
      <p>2. Set your desired output dimensions</p>
      <p>3. Click "Generate" to create a resized image with your source image centered and filled</p>
      <p>4. Your processed image will be displayed with a download link</p>
    </div>
    <div class="credits">
      Version $VERSION /
      Made by <a href="https://links.davecross.co.uk/">Dave Cross</a> /



( run in 1.627 second using v1.01-cache-2.11-cpan-ceb78f64989 )