modulojs / modulo

A drop-in JavaScript framework for modular web components, kept to about 2000 lines
https://modulojs.org/
GNU Lesser General Public License v2.1
12 stars 1 forks source link

Image: Component part for image bundling / prerendered effects #41

Open michaelpb opened 1 year ago

michaelpb commented 1 year ago

As a user of Modulo, I often want to do various image pre-processing, such as adjusting colors, cropping, or resizing large source images, and it would be convenient if that could be done at build time, even if I am using Modulo in the browser.


<Template>
   Look at this: <img src="{{ my_image.src }}" />
</Template>
<Image
    -media-src="/my-image.png"
    name="my_image"
    filter="
        sepia(0.4)
        blur(10px)
    "
></Image>

Or, for further customization, the canvas would be available:

<script Image  -media-src="/base-image.png">
  ctx.font = "48px serif";
  ctx.fillText("Hello world", 10, 50);
</script>

We can also run this on render, so it can do non-static templating (requires mode="embed" so it won't discard the original)

<Template>
    <img src="{{ text_image.src }}" />
    <input name="caption" [state.bind] />
</Template>
<State
    caption="Hello Image Scripting World!"
></State>
<script Image mode="embed" -media-src="/base-image.png" name="text_image">
  ctx.font = "48px serif"
  ctx.fillText(state.caption, 10, 50)
</script>

This allows for tons of processing-style image manipulation.


Create a succinct image processing system that uses the ctx.filter property (see below) to load image.

Could be done in <50 lines of code, and would be pretty useful and important.

https://stackoverflow.com/questions/30408939/how-to-save-image-from-canvas-with-css-filters

var img = new Image();
img.crossOrigin = ""; 
img.onload = draw;
img.src = "//i.imgur.com/WblO1jx.jpg";

function  draw() {
  var canvas = document.querySelector("canvas"),
      ctx = canvas.getContext("2d");
  canvas.width = img.width;
  canvas.height = img.height;
  // filter  if (typeof ctx.filter !== "undefined") {
  ctx.filter = "sepia(0.8)";
  ctx.drawImage(img, 0, 0);
  document.querySelector("img").src = canvas.toDataURL();
}
michaelpb commented 1 year ago

Work on it:

    1111 
    1112 modulo.register('processor', function imageSrc (modulo, def, value, callback = null) {
    1113     const { getParentDefPath, keyFilter } = modulo.registry.utils;
    1114     const img = new window.Image();
    1115     img.crossOrigin = '';
    1116     img.onload = () => {
    1117         const canvas = document.createElement('canvas');
    1118         const ctx = canvas.getContext('2d');
    1119         canvas.width = img.width;
    1120         canvas.height = img.height;
    1121         const isLower = key => key[0].toLowerCase() === key[0];
    1122         Object.assign(ctx, keyFilter(def, isLower));
    1123         if (callback) {
    1124             callback(ctx, canvas, img);
    1125         }
    1126         ctx.drawImage(img, 0, 0);
    1127         def.ImageContent = canvas.toDataURL();
    1128     };
    1129     //img.src = value.startsWith('data:') ? value :
    1130     //    (new URL(value, getParentDefPath(modulo, def))).href;
    1131     img.src = 'http://livesyllabus.com/img/left_side_rasterized.png';
    1132 });

        1134 modulo.register('cpart', class Image {
        1135     initializedCallback(renderObj) { // TODO: Refactor with Template
        1136         const engine = this.conf.engine || 'ImageTemplater';
        1137         this.canvas = window.document.createElement('canvas');
        1138         this.ctx = this.canvas.getContext('2d');
        1139         Object.assign(this.canvas, { height: 1, width: 1 });
        1140         this.templater = new this.modulo.registry.engines[engine](this.modulo, this.conf);
        1141         this.templater.modes.text = (text) => {
        1142             const img = new window.Image();
        1143             img.crossOrigin = '';
        1144             img.onload = () => {
        1145                 canvas.width = img.width;
        1146                 canvas.height = img.height;
        1147                 const isLower = key => key[0].toLowerCase() === key[0];
        1148                 Object.assign(ctx, keyFilter(def, isLower));
        1149                 if (callback) {
        1150                     callback(ctx, canvas, img);
        1151                 }
        1152                 ctx.drawImage(img, 0, 0);
        1153                 def.ImageContent = canvas.toDataURL();
        1154             };
        1155             img.src
        1156             return '';
        1157         };
        1158         const render = this.templater.render.bind(this.templater);
        1159         return { render }; // Expose render to include, renderas etc
        1160     }        

    1161     prepareCallback(renderObj) {
    1162         if (!this.def && this.conf) { this.def = this.conf; } // XXX rm
    1163         return {
    1164             src: this.ImageContent,
    1165         };
    1166     }
    1167 }, {
    1168     lifecycle: null,
    1169     engine: 'ImageTemplater',
    1170     TemplatePrebuild: "yes",
    1171     //DefLoaders: [ 'DefinedAs', 'ImageSrc', 'Src' ],
    1172     DefFinalizers: [ 'TemplatePrebuild' ]
    1173     //DefBuilders: [ 'Content|Code' ],
    1174     //TODO: Use templater as follows: Every method becomes a templatetag:
    1175     // {% begin-path %} {% fill-rect 0 0 100 100 %} etc
    1176     // The text output gets split along newlines, and every value becomes
    1177     // kind of a DSL that is fed into imageSrc:
    1178     // - URL to an iamge (https://cdn.com/asdf.png is valid)
    1179     // - Data URL (e.g.  data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)
    1180     // - JSON prefix ({"filter": ... })
    1181     // Run once during render to generate base canvas (no images will have
    1182     // loaded)
    1183     // Then, after the last image loads, does another async callback to paint
    1184     // over images (but skips image load attempts)
    1185 });
    1186