0

When using this code to sort pixels, the code works fine with the default image when hosted online. However, when testing the script offline, the default image loads but does not process correctly through the sorter. What is causing the behavior to work in an online hosted environment but not in a localized, offline environment?

See online code here: http://birdhat.org/misc/sort-pixels/

var READY_STATUS = ' '
var FIRSTLOAD_STATUS = ' ';
var SORTING_STATUS = 'Sorting (be patient)';
var FINISHING_STATUS = 'Finishing up...';
var FILENAME = null;
var SORTING_FILENAME = null;
var STATE = 'ready';

//================================================================================
// UTILS

var rgb2hsv = function(r, g, b) {
  // given r,g,b in the range 0-255,
  // return [h,s,v] in the range 0-1
  var rr, gg, bb,
    r = r / 255,
    g = g / 255,
    b = b / 255,
    h, s,
    v = Math.max(r, g, b),
    diff = v - Math.min(r, g, b),
    diffc = function(c) {
      return (v - c) / 6 / diff + 1 / 2;
    };
  if (diff == 0) {
    h = s = 0;
  } else {
    s = diff / v;
    rr = diffc(r);
    gg = diffc(g);
    bb = diffc(b);

    if (r === v) {
      h = bb - gg;
    } else if (g === v) {
      h = (1 / 3) + rr - bb;
    } else if (b === v) {
      h = (2 / 3) + gg - rr;
    }

    if (h < 0) {
      h += 1;
    } else if (h > 1) {
      h -= 1;
    }
  }
  return [h, s, v];
};

//================================================================================
// INTERFACE

var setStatus = function(msg) {
  document.getElementById('status').innerHTML = msg;
};

//================================================================================
// SORTING

var sortOneStep = function(sort_list, my_canvas, context, ii) {
  // do one step of sorting and queue up the next one

  var first_is_horizontal = 1;

  // on first step, remember the filename
  if (ii == 0) {
    SORTING_FILENAME = FILENAME;
  }

  // if the filename has changed, stop
  if (ii > 0 && FILENAME != SORTING_FILENAME) {
    finish(my_canvas, false); // need to avoid copy here or something
    return;
  }

  // stop when we've finished all the sorts
  if (ii >= sort_list.length) {
    finish(my_canvas, true);
    return;
  }

  sortImage(my_canvas, context, ii, (ii + first_is_horizontal) % 2, sort_list[ii]);
  var bar_chars = [];
  for (var char_ii = 0; char_ii < sort_list.length; char_ii++) {
    if (char_ii <= ii) {
      bar_chars.push('=');
    } else {
      bar_chars.push('&nbsp;');
    }
  }
  setStatus(SORTING_STATUS + ' <code>[' + bar_chars.join('') + ']</code>');
  var timeout_length = 100;
  //if (ii === 1) { timeout_length = 2500; }
  setTimeout(function() {
    sortOneStep(sort_list, my_canvas, context, ii + 1);
  }, timeout_length);
};

var finish = function(my_canvas, done) {
  setStatus(FINISHING_STATUS);
  var my_image = document.getElementById('my-image');
  if (done) {
    my_image.src = my_canvas.toDataURL('image/png');
  }
  my_image.className = '';
  my_canvas.className = 'invisible';
  setStatus(READY_STATUS);
  console.log('DONE');
  STATE = 'ready';
  document.getElementById('sort-button-quick').className = 'ready-button';
  document.getElementById('sort-button-thorough').className = 'ready-button';
}

var sortImage = function(my_canvas, context, ii, is_horizontal, kind) {
  // sort the entire image

  var dir_name;
  if (is_horizontal) {
    dir_name = 'horizontal';
    for (var y = 0; y < my_canvas.height; y += 1) {
      sortRegion(context, 0, y, my_canvas.width, 1, kind);
    }
  } else {
    dir_name = 'vertical';
    for (var x = 0; x < my_canvas.width; x += 1) {
      sortRegion(context, x, 0, 1, my_canvas.height, kind);
    }
  }
  console.log('' + ii + ' -- sorted ' + dir_name + ' by ' + kind);
};

var sortRegion = function(context, x, y, width, height, kind) {
  // sort a rectangular region of the image, usually a single row or column of pixels
  // kind should be one of: random, semirandom, v, h, h2, s, magic

  // fetch image data
  var imgData = context.getImageData(x, y, width, height);
  var data = imgData.data;

  // convert to sortable pixel array
  var pixels = [];
  for (var ii = 0; ii < imgData.data.length; ii += 4) {
    var pixel = [data[ii], data[ii + 1], data[ii + 2]];

    switch (kind) {
      case 'v':
        var sortValue = -(pixel[0] + pixel[1] + pixel[2]);
        var sortValue = -(pixel[0] * 0.30 + pixel[1] * 0.59 + pixel[2] * 0.11);
        break;
      case 'semirandom':
        var sortValue = ii / 4 + Math.random() * 25;
        break;
      case 'random':
        var sortValue = Math.random();
        break;
      default:
        hsv = rgb2hsv(pixel[0], pixel[1], pixel[2]);
        var h = hsv[0];
        var s = hsv[1];
        var v = (pixel[0] + pixel[1] + pixel[2]) / (255 * 3);
        if (kind === 'magic') {
          var sortValue = 1 - ((h + 0.2) % 1);
          if (s < 0.07) {
            if (v < 0.5) {
              sortValue = v - 300;
            } else {
              sortValue = v + 300;
            }
          } else {
            if (v < 0.2) {
              sortValue = v - 300;
            };
            if (v > 0.9) {
              sortValue = v + 300;
            };
          }
        } else if (kind === 'h') {
          var sortValue = h;
        } else if (kind === 'h2') {
          var sortValue = (h + 0.15) % 1;
          if (s < 0.07) {
            sortValue -= 900;
          }
        } else if (kind === 's') {
          var sortValue = s;
        }
    }

    pixels.push([sortValue, pixel]);
  }

  // sort
  pixels.sort(function(a, b) {
    return a[0] - b[0];
  });

  // write back to data array and then back to canvas
  for (var jj = 0; jj < pixels.length; jj++) {
    var pixel = pixels[jj][1];
    data[jj * 4 + 0] = pixel[0];
    data[jj * 4 + 1] = pixel[1];
    data[jj * 4 + 2] = pixel[2];
  }
  context.putImageData(imgData, x, y);

};

//================================================================================
// MAIN
var wo = window.onload;
window.onload = function() {
  wo && wo.call(null);

  setStatus(FIRSTLOAD_STATUS);


  var handleDragOver = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy';
    dropZone.className = 'big-border';
  };

  var handleDragLeave = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    dropZone.className = '';
  };

  var handleFileDrop = function(evt) {
    // when the user drops a file,
    // load it into an img tag
    // and then copy the data into the canvas
    console.log('[handleFileDrop]');
    evt.stopPropagation();
    evt.preventDefault();

    dropZone.className = '';

    var files = evt.dataTransfer.files;

    // only allow one file to be dropped
    if (files.length !== 1) {
      return;
    }

    for (var ii = 0; ii < files.length; ii++) {
      var file = files[ii];
      FILENAME = file.name;
      console.log(FILENAME);

      // only allow images
      if (!file.type.match('image.*')) {
        continue;
      }

      var reader = new FileReader();
      reader.onload = (function(theFile) {
        return function(e) {
          droppedImageTag = document.getElementById('my-image').src = e.target.result;
        };
      })(file);

      reader.readAsDataURL(file);
    }
  };

  var body = document.getElementById('body');
  var dropZone = document.getElementById('drop-zone');
  body.addEventListener('dragover', handleDragOver, false);
  body.addEventListener('dragleave', handleDragLeave, false);
  body.addEventListener('drop', handleFileDrop, false);

  var copyImgToCanvas = function(image_id, canvas_id) {
    console.log('[copyImgToCanvas] ' + image_id + ' --> ' + canvas_id);
    var my_image = document.getElementById(image_id);
    var my_canvas = document.getElementById(canvas_id);
    var context = my_canvas.getContext('2d');

    my_canvas.width = my_image.width;
    my_canvas.height = my_image.height;

    my_canvas.className = '';
    my_image.className = 'invisible';

    context.drawImage(my_image, 0, 0);
  };

  var handleButtonClick = function() {
    STATE = 'sorting';
    console.log('[click]');
    setStatus(SORTING_STATUS);
    document.getElementById('sort-button-quick').className = 'disabled-button';
    document.getElementById('sort-button-thorough').className = 'disabled-button';
  };

  var launchSorting = function(sort_list) {
    var my_canvas = document.getElementById('my-canvas');
    var context = my_canvas.getContext('2d');
    copyImgToCanvas('my-image', 'my-canvas');
    sortOneStep(sort_list, my_canvas, context, 0);
  };

  document.getElementById('sort-button-quick').onclick = function() {
    if (STATE === 'sorting') {
      return;
    }
    handleButtonClick();
    // random, semirandom, v, h, h2, s, magic
    var sort_list = ['semirandom', 'v']
    setTimeout(function() {
      launchSorting(sort_list)
    }, 1)
  };
  document.getElementById('sort-button-thorough').onclick = function() {
    if (STATE === 'sorting') {
      return;
    }
    handleButtonClick();
    // random, semirandom, v, h, h2, s, magic
    var sort_list = ['h2', 'v', 'h2', 'v', 'h2', 'v', 'h2', 'v', 'h2', 'v', 'h2', 'v', ];
    setTimeout(function() {
      launchSorting(sort_list)
    }, 1)
  };
  document.getElementById('sort-button-quick').className = 'ready-button';
  document.getElementById('sort-button-thorough').className = 'ready-button';
};
body {
  text-align: center;
  padding: 0px;
  margin: 0px;
  color: #bbb;
  font-family: 'Helvetica Neue', Helvetica, sans-serif;
  background: #333;
  font-size: 14px;
  line-height: 20px;
}

#copy {
  background: #000;
  padding: 10px;
  padding-left: 30px;
  padding-right: 30px;
  text-align: left;
}

#topbar {
  text-align: left;
}

#sort-button-quick {
  padding: 12px;
  display: inline-block;
  text-align: center;
  border-style: solid;
  border-width: 2px;
  margin-left: 30px;
}

#sort-button-thorough {
  padding: 12px;
  display: inline-block;
  text-align: center;
  border-style: solid;
  border-width: 2px;
  margin-left: 15px;
}

.ready-button {
  background: #777;
  color: white;
  border-color: white;
}

.ready-button>a {
  color: white;
  text-decoration: none;
}

.disabled-button {
  font-style: italic;
  background: #606060;
  color: #ccc;
  border-color: #bbb;
}

.disabled-button>a {
  color: #ccc;
  text-decoration: none;
}

#status {
  padding: 12px;
  display: inline-block;
  color: #ff8;
}

#drop-zone-wrapper {
  background: #000;
}

#drop-zone {
  padding-top: 50px;
  padding-bottom: 50px;
  background: #000;
  border-radius: 30px;
  border-style: dashed;
  border-color: black;
  border-width: 10px;
}

.big-border {
  border-color: #aaa !important;
}

.invisible {
  display: none;
}

.dotted {
  border-style: dotted;
  border-color: red;
  border-width: 1px;
}
<!DOCTYPE html>
<html>

<head>
</head>

<body id="body">

  <div id="copy">
    <h3> Image pixel sorter </h3>
    <p>
      Drag an image from your computer into this page, then click Sort. The pixels will be sorted vertically by brightness and horizontally by hue.
    </p>
    <p>
      To use an image from the web, drag it to your desktop first and then into this page. Use small images or it will be very slow.
    </p>
    <p>
      <a href="http://sorted-pixels.tumblr.com/" style="color:#8af;">Blog of results</a>
    </p>
  </div>

  <div id="topbar">
    <div id="sort-button-quick" class="disabled-button"><a> Two-Step Sort </a></div>
    <div id="sort-button-thorough" class="disabled-button"><a> Twelve-Step Sort </a></div>
    <div id="status">&nbsp;</div>
  </div>

  <div id="drop-zone-wrapper">
    <div id="drop-zone">
      <canvas id="my-canvas" width=100 height=100 class="invisible"></canvas>
      <img id="my-image" src="img/sunset.png">
    </div>
  </div>

</body>

</html>

I've tried moving the images, rerouting the sources for the loading of them, moving the image loading into the onload function... I have the sunset.png default image in an img folder with code. It displays correctly, but does not execute the pixel sorting when offline.

Barmar
  • 741,623
  • 53
  • 500
  • 612
pimm245
  • 13
  • 3
  • Websites don't work offline as default, to allow websites to work offline you need to implement a service worker. More details -> https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API – Keith Apr 19 '23 at 20:53
  • 3
    @Keith What are you talking about? You can open a local .html file and it will work fine, as long as it doesn't need other server resources. – Barmar Apr 19 '23 at 20:54
  • 1
    I can't reproduce the problem. I copied your code to local `.html`, `.css`, and `.js` files and it works the same as in the snippet above. – Barmar Apr 19 '23 at 20:55
  • 1
    @Barmar That's not the definition of offline, of course you can access files locally if you use `file` protocol, but what makes you think that's what the OP meant by offline. – Keith Apr 19 '23 at 20:56
  • Errors in console? – James Apr 19 '23 at 20:56
  • 1
    @Keith Because they say that the page opens, so they're not talking about not being able to connect to the server containing the webpage. – Barmar Apr 19 '23 at 20:58
  • 1
    I wasn't testing with the default image. Now I see it. The console has the error "Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data." – Barmar Apr 19 '23 at 21:02
  • @Barmar So strictly speaking, he's not offline then.. :) – Keith Apr 19 '23 at 21:04
  • @Keith He's just distinguishing local testing with remote hosting. – Barmar Apr 19 '23 at 21:04
  • 1
    Just noted an error in the console per James's comment... "Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data." – pimm245 Apr 19 '23 at 21:05
  • Filesystem files aren't considered to be within the same domain (!) You can avoid it by embedding sunset.png into your `` tag using a [data-uri](https://stackoverflow.com/questions/18089630/what-is-the-purpose-of-data-uris). – James Apr 22 '23 at 01:33

0 Answers0