Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
michaelpb opened this issue Mar 13, 2023 · 1 comment
Open

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

michaelpb opened this issue Mar 13, 2023 · 1 comment
Labels
enhancement New feature or request

Comments

@michaelpb
Copy link
Contributor

michaelpb commented Mar 13, 2023

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.

  • <Image> implementation:
  • Bundle image as base64 encoded in JS (E.g. something like -media-src= or -bin-src= or -src64= etc)
  • Has a PreProcessor that applies filters and then re-encodes as base64
  • If mode="asset", then during build, exports as hashed png and delete def (default), otherwise if mode="embed", keep in base64 in JS (hope that gzip will make less wastefull!)
  • If mode="embed", allows for <img src="{{ my_image|cssfilter:'sepia(0.5)' }}" /> - e.g. live, template-rendering-time image filtering

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 michaelpb added the enhancement New feature or request label Mar 13, 2023
@michaelpb
Copy link
Contributor Author

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 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant