123

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <section>
  <div>
<h3>Alpha rgb blacken & trim threshold:</h3>    
  <div class="slider athreshold"></div><span class="number">0</span>
  </div>
  <div>
  <h3>How to handle color of fully transparent pixels:</h3>
  <label><input type="radio" name="tpixels" value="avg" checked /> Use average color</label><br/>
  <label><input type="radio" name="tpixels" value="exp"/> Expand color</label>
    <label><input type="radio" name="tpixels" value="expblur"/> Expand color with blur</label>
  </div>
  <div>
  <h3>Upload transparent PNG File:</h3>
  <input type="file" id="files" name="files[]"/><br/>
  <label><input type="checkbox" name="trim"/> Trim Transparent areas</label>
  </div>
</section>
<section>
  <div>
  <h3>Compression:</h3>
    <div class="slider comp"></div><span class="number">65%</span>  
  </div>
  <div>
    <h3>Result:</h3>
    <output id="list"></output>
  </div>
  <div>
    <h3>Should I use SVG or Canvas?</h3>
    <p>
      SVG is easier to implement and doesn't need any JavaScript. But if you plan to animate the image, each frame will be recalculated by the browser if you rotate or scale it - which might be slow.
    </p>
    <p>
      Canvas needs some JavaScript to work and maybe some time for pre-calculation (at least for huge images this will be noticeable). Use this if you want to animate the image later on. A Canvas will act just like a transparent PNG - nothing to be recalculated once it is filled with pixels.
    </p>
  </div>
  <div>
  <h3>Code SVG (with external JPG source, use for inline):</h3>
  <textarea id="code" class="code"></textarea>    
  </div>
  <div>
    <h3>Code SVG with embedded JPG source, this can be put inline in HTML code.</h3>
    <textarea id="codeembedded" class="code"></textarea>
  </div>
  <div>
    <h3>Now it gets deep: complete SVG with embedded image as "optimized encoded" data URL!</h3>
    <p>You can use this Data-URL as image source. It will gzip better than base64-encoding the svg - containing already a base64-encoded image - again.<br/>    
    Big thanks go to Taylor Hunt for <a href="https://codepen.io/tigt/post/optimizing-svgs-in-data-uris" target="_blank">pointing out how to do this properly.</a><br/>
      They're very easy to use: <a href="https://codepen.io/ninili/pen/XjKzKB" target="_blank">Take a look at this pen</a>
    </p>
      <textarea id="codesvg64" class="code"></textarea>
  </div>
  <div>
    <h3>Code Canvas:</h3>
    <a href="https://codepen.io/ninili/pen/zKArEg" target="_blank">Take a look at this pen</a>
  </div>
    <div>
    <h3>Image Base64 (for Data URLs):</h3>
      <p>
      you might notice that some JPGs, especially with large empty areas yield a lot of repetitions in the base64 code and you wonder why that is, because you always thought of JPG to be a highly compressed image format. But the JPGs the browser creates don't have optimized huffman tables (the option you can check in Photoshop "Save as Web" => "optimized")
      </p>
    <textarea id="base64" class="code"></textarea>
  </div>
</section>
            
          
!
            
              body {
  background:#000;
  color:#ffffff;
  padding:15px;
}
* {
  box-sizing:border-box;
}
h1,h2,h3 {
  margin:10px 0;
  //background:linear-gradient(to right,rgba(51,51,51,0),#333 50%), linear-gradient(to bottom, #000,rgba(0,0,0,0));
  background:linear-gradient(#777,rgba(255,255,255,0));
  // border:1px dotted #ccc;
  text-shadow:1px 1px 2px rgba(0,0,0,0.75);
  box-shadow:0px -2px 2px rgba(0,0,0,0.5);
  //border-bottom:none;
  border-radius:10px 10px 0 0;
  padding:4px 10px;
}
.slider {
  width:300px;
  display:inline-block;
}
.container > * {
  max-width:50%;
}
.container:hover > svg {
  max-width:none;
}
a {
  color:#eeeeee;
}
.code {
  width:95%;
  height:20vh;
  margin:20px;
}
section {  
  border:#000 2px solid;
  padding:20px;
  background:radial-gradient(#999,#333),linear-gradient(#666 0px,rgba(0,0,0,0) 15px),linear-gradient(to top,#666 0px,rgba(0,0,0,0) 15px),linear-gradient(to right, #666 0px, rgba(0,0,0,0) 20px),linear-gradient(to left, #666 0px, rgba(0,0,0,0) 20px);
  &:not(:last-of-type) {
     border-bottom:none; 
  }  
  &:first-of-type {
    border-radius:20px 20px 0 0;
    column-width: 400px;
  }
  &:last-of-type {
    border-radius:0 0 20px 20px;
  }
  > div:not(.slider) {
    break-inside:avoid;
    padding:5px 10px 15px 10px;  
  }
  .number, a[download], .container span {
    font-size:20px;
    margin:5px;
    padding:10px;
    background:#333;
    border-radius:10px;
  }
  .container {
    a, span {
      display:block;
      text-align:center;
    }
  }
  input {
    margin-top:10px;
  }
}
            
          
!
            
                var imageCVs = [],
      $output = $('#list'),
      mskID = 0;

  function trimImageData(imgData) {
    var top = null,
      bottom = null,
      left = null,
      right = null,
      dir = 1,
      iw4 = imgData.width * 4,
      thresh = $('.slider.athreshold').slider("values", 0);
    outer:
      for (var yc = 0; yc < imgData.height; yc += dir) {
        var aa = false,
          pos = yc * iw4 + 3;
        for (var xc = 0; xc < imgData.width; xc++, pos += 4) {
          if (imgData.data[pos] > thresh) {
            aa = true;
            break;
          }
        }
        if (aa && dir > 0 && top === null) {
          top = yc;
          dir = -1;
          yc = imgData.height;
        } else if (aa && dir < 0 && bottom === null) {
          bottom = yc + 1;
          break outer;
        }
      }
    console.log("Bottom:" + bottom + " (" + imgData.height + ")");
    if (top === null) top = 0;
    if (bottom === null) bottom = imgData.height;
    dir = 1;
    var topOff = top * iw4;
    outer2:
      for (var xc = 0; xc < imgData.width; xc += dir) {
        var pos = topOff + (xc * 4) + 3,
          aa = false;
        for (var yc = top; yc < bottom; yc++, pos += iw4) {
          if (imgData.data[pos] > thresh) {
            aa = true;
            break;
          }
        }
        if (aa && dir > 0 && left === null) {
          left = xc;
          dir = -1;
          xc = imgData.width;
        } else if (aa && dir < 0 && right === null) {
          right = xc + 1;
          break outer2;
        }
      }
    console.log("left:" + left + " right:" + right + "(" + imgData.width + ")");
    if (left === null) left = 0;
    if (right === null) right = imgData.width;
    if (left == 0 && top == 0 && bottom == imgData.height && right == imgData.width) return imgData;
    var cv = $('<canvas/>')[0],
      ctx = cv.getContext('2d');
    cv.width = right - left;
    cv.height = bottom - top;
    ctx.putImageData(imgData, -left, -top);
    imgData = ctx.getImageData(0, 0, cv.width, cv.height);
    return imgData;
  }

  var img;

  function reMask() {
    if (img) {
      $(img).trigger('load');
    } 
  }

  function handleFileSelect(evt) {
    var files = evt.target.files;    
    for (var i = 0, f; f = files[i]; i++) {      
      if (!f.type.match('image.*')) {
        continue;
      }
      var reader = new FileReader();
      
      reader.onload = (function(theFile) {
        return function(e) {

          var $cv = $('<canvas/>'),
            cv = $cv[0],
            ctx = cv.getContext('2d'),
            imgData,
            imgDataAlpha,            
            $span = $('<span/>'),
            $jpg = $('<img/>'),
            paintBack = false;
          
          img = new Image();
          img.onload = function() {
            var w = img.width,
                h = img.height;
            cv.width = w;
            cv.height = h * 2;
            ctx.drawImage(img, 0, 0);
            imgData = ctx.getImageData(0, 0, w, h);
            if ($('#trim').prop('checked')) {
              imgData = trimImageData(imgData);
              w = imgData.width;
              h = imgData.height;
              cv.width = w;
              cv.height = h * 2;
            }            
            imgDataAlpha = ctx.createImageData(w, h);
            
            var fillMode = $('input[name=tpixels]:checked').val(),
                pos = 0,
                imgDataCopy = new Uint8ClampedArray(imgData.data),
                thresh = $('.slider.athreshold').slider("values", 0);                
            
            if (thresh < 1) thresh = 1;            
            if (fillMode == 'avg') {
              var allR, allG, allB, ca = 0;
              allR = 0;
              allG = 0;
              allB = 0;
              for (var yc = 0; yc < h; yc++) {
                var rowR = 0,
                    rowG = 0,
                    rowB = 0,
                    cRow = 0;
                for (var xc = 0; xc < w; xc++, pos += 4) {
                  var a = imgData.data[pos + 3];
                  imgData.data[pos + 3] = 0xff;
                  if (a < thresh) {
                    a = 0;
                  } else {
                    rowR += imgData.data[pos];
                    rowG += imgData.data[pos + 1];
                    rowB += imgData.data[pos + 2];
                    cRow++;
                  }
                  imgDataAlpha.data[pos] = a;
                  imgDataAlpha.data[pos + 1] = a;
                  imgDataAlpha.data[pos + 2] = a;
                  imgDataAlpha.data[pos + 3] = 0xff;
                }
                if (cRow) {
                  rowR /= cRow;
                  rowG /= cRow;
                  rowB /= cRow;
                  allR += rowR;
                  allG += rowG;
                  allB += rowB;
                  ca++;
                }
              }
              if (ca) {
                allR /= ca;
                allG /= ca;
                allB /= ca;
              }
              for (pos = 0, yc = 0; yc < h; yc++) {
                for (xc = 0; xc < w; xc++, pos += 4) {
                  var a = imgDataCopy[pos + 3];
                  if (a < thresh) {
                    imgData.data[pos] = allR;
                    imgData.data[pos + 1] = allG;
                    imgData.data[pos + 2] = allB;
                  }
                }
              }
            } else {              
              var lastPos = false;
              var spans = [];
              pos = w*h*4;
              while (pos-=4) {
                var a = imgDataCopy[pos+3];
                if (a < thresh) {
                  a=0;
                  if (lastPos===false) lastPos = pos;
                } else {
                  if (lastPos!==false) {
                    spans.push({
                      r:imgData.data[pos],
                      g:imgData.data[pos+1],
                      b:imgData.data[pos+2],
                      from:pos+4,
                      to:lastPos
                    });
                    lastPos = false;
                  }
                }
                imgDataAlpha.data[pos] = a;
                imgDataAlpha.data[pos + 1] = a;
                imgDataAlpha.data[pos + 2] = a;
                imgDataAlpha.data[pos + 3] = 0xff;
                imgData.data[pos+3] = 255;
              }
              if (lastPos!==false && spans.length) {
                var lastSpan = spans[spans.length-1];
                spans.push({
                  r:lastSpan.r,
                  g:lastSpan.g,
                  b:lastSpan.b,
                  from:0,
                  to:lastPos
                });
              }
              var i = spans.length;
              while (i--) {
                var span = spans[i];
                for (pos = span.from; pos <= span.to; pos+=4) {                               imgData.data[pos] = span.r;
                  imgData.data[pos+1] = span.g;
                  imgData.data[pos+2] = span.b;
                  
                }
              }
              
              if (fillMode=="expblur") {
                for (var i=0;i<8;i++) {
                 for (xc=0;xc<w;xc++) {
                   pos = xc*4;
                   var lastColorR = -1;
                   var lastColorB = -1;
                   var lastColorG = -1;
                   for (yc=0;yc<h;yc++,pos+=w*4) {
                     var a = imgDataCopy[pos+3];
                     if (a<thresh) {
                       if (lastColorR>=0) {
                         var r = imgData.data[pos],
                             g = imgData.data[pos+1],
                             b = imgData.data[pos+2];
                         imgData.data[pos] = lastColorR;
                         imgData.data[pos+1] = lastColorG;
                         imgData.data[pos+2] = lastColorB;
                         lastColorR += r;
                         lastColorG += g;
                         lastColorB += b;
                         lastColorR *=0.5;
                         lastColorG *=0.5;
                         lastColorB *=0.5;
                       } else {
                         lastColorR = imgData.data[pos];
                         lastColorG = imgData.data[pos+1];
                         lastColorB = imgData.data[pos+2];
                       }
                     }
                   }
                 }
               }
              }
            }

            ctx.clearRect(0, 0, cv.width, cv.height);
            ctx.putImageData(imgData, 0, 0);
            ctx.putImageData(imgDataAlpha, 0, h);
            imageCVs.push(cv);
            insertCVasJPG(cv);
          }
          img.src = e.target.result;
        };
      })(f);

      // Read in the image file as a data URL.
      reader.readAsDataURL(f);
    }
  }

  function alphaSVG(imgURL,w,h) {
    var h2 = h * 0.5;
    var svg =
        '<defs><mask id="msk">'+
        '<image xlink:href="' + imgURL + '" x="0" y="' + (-h2) + '" width="' + w + '" height="' + h + '"/>'+
        '</mask></defs>' +
      '<image xlink:href="' + imgURL + '" width="' + w + '" height="' + h + '" mask="url(#msk)"/>';
    return svg;
  }

function svgCode(w,h) {
  var h2 = h*0.5;
  var svg =
      "<!-- replace myImage.jpg with actual image -->\n"+
      "<!-- remember to use different mask IDs for multiple images in one document -->\n"+
      "<svg width=\""+w+"\" height=\""+h2+"\">\n"+
      alphaSVG('myImage.jpg',w,h)+"\n"+
       "</svg>";
  return svg;
}

function svgCodeEmbedded(imgURL, w, h) {
  var h2 = h*0.5;
  var svg = 
      "<svg width=\""+w+"\" height=\""+h2+"\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"+
      "\t<defs>\n"+
      "\t\t<image id=\"i\" x=\"0\" y=\"0\" width=\""+w+"\" height=\""+h+"\" xlink:href=\""+imgURL+"\" />\n"+
      "\t\t<mask id=\"m\"><use xlink:href=\"#i\" transform=\"translate(0,"+(-h2)+")\"/></mask>\n"+
      "\t</defs>"+
      "<use xlink:href=\"#i\" mask=\"url(#m)\"/>\n"+
      "</svg>";  
  return svg;
}

function urlEncodeSVG(svg) {
  svg = svg.replace(/"/g,"'").replace(/[\t\n\r]/g,'').replace(/[%&#<>{}]/g,function(ch) {return '%'+ch.charCodeAt(0).toString(16);});
  return 'data:image/svg+xml,'+svg;
}

  function testAsSVG(imgURL, w, h) {
    var h2 = h * 0.5,
      svg =
      '<svg width="' + w + '" height="' + h2 + '"><pattern id="muster_a" patternUnits="userSpaceOnUse" width="40" height="40" x="0" y="0"><g stroke="none" fill="#666666"><rect width="20" height="20"/><rect width="20" height="20" x="20" y="20" /></g><g stroke="none" fill="#aaaaaa"><rect width="20" height="20" x="20"/><rect width="20" height="20" y="20"/></g></pattern><rect width="' + (w + 80) + '" height="' + (h2 + 40) + '" fill="url(#muster_a)"><animateTransform attributeName="transform" type="translate" dur="3s" from="-80,-40" to="0,0" repeatCount="indefinite"/></rect>' +
      alphaSVG(imgURL,w,h) +
      '</svg>';
    mskID++;
    return svg;
  }

  function insertCVasJPG(cv) {
    var sliderValue = $('.slider.comp').slider("values", 0) * 0.01,
      dataURL = cv.toDataURL('image/jpeg', sliderValue),
      $img = $('<img/>'),
      $span = $('<span/>'),
      $dl = $('<a/>'),
      $container = $('<div/>').addClass('container');
    $('.slider.comp').next().text(Math.round(sliderValue*100)+'%');
    
    $output.empty();
    $('#base64').text(dataURL);
    $img.attr('src', dataURL);
    $dl.attr('href', dataURL).text('download JPG').attr('download', 'gfx');
    $span.text('approx. size:' + Math.floor(dataURL.length * 6 / 8) + " bytes");
    $span.appendTo($container);
    $dl.appendTo($container);
    $container.append('<br/>');
    $img.appendTo($container);
    $container.append(testAsSVG(dataURL, cv.width, cv.height));
    $('#code').text(svgCode(cv.width,cv.height));
    var svgEmbedded = svgCodeEmbedded(dataURL, cv.width, cv.height);
    $('#codesvg64').text(urlEncodeSVG(svgEmbedded));
    $('#codeembedded').text(svgEmbedded);
    var svgBlob = new Blob([svgEmbedded], {type: 'image/svg+xml'});
    $dl.after(
      $('<a/>').attr('href',window.URL.createObjectURL(svgBlob)).text('Download SVG').attr('download','gfx')
    );
    $container.appendTo($output);
    $('svg').mouseover(function() {
      $(this).css({
        transformOrigin: '0 0',
        transform: 'scale(2)'
      })
    }).mouseout(function() {
      $(this).css({
        transformOrigin: '0 0',
        transform: 'scale(1)'
      })
    });
  }

  function rePack() {
    $('output').empty();
    $.each(imageCVs, function() {
      insertCVasJPG(this);
    });
  }

  $('#files').on('change', handleFileSelect);
  $('input[name=tpixels], input[name=trim]').on('change', reMask);
  $('.slider.comp').slider({
    value: 65,
    orientation: "horizontal",
    min: 0,
    max: 100,
    change: rePack
  });
  $('.slider.athreshold').slider({
    value: 0,
    orientation: "horizontal",
    min: 0,
    max: 255,
    change: function() {
      var $s = $('.slider.athreshold');
      var v = $s.slider("values", 0);
      $s.next().text(v);
      reMask();
    }
  });
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console