Skip to content

Commit 07b90d3

Browse files
authored
Merge pull request #790 from jonobr1/653-image
Add Image class for rendering images with fit modes
2 parents b4346be + 073e997 commit 07b90d3

23 files changed

+1721
-121
lines changed

build/two.js

Lines changed: 174 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ var Two = (() => {
6767
};
6868
return elem;
6969
},
70-
polyfill: function(canvas3, Image) {
70+
polyfill: function(canvas3, Image2) {
7171
CanvasPolyfill.shim(canvas3);
72-
if (typeof Image !== "undefined") {
73-
CanvasPolyfill.Image = Image;
72+
if (typeof Image2 !== "undefined") {
73+
CanvasPolyfill.Image = Image2;
7474
}
7575
CanvasPolyfill.isHeadless = true;
7676
return canvas3;
@@ -766,7 +766,7 @@ var Two = (() => {
766766
canvas: "CanvasRenderer"
767767
},
768768
Version: "v0.8.20",
769-
PublishDate: "2025-08-07T03:41:10.194Z",
769+
PublishDate: "2025-08-08T23:50:19.761Z",
770770
Identifier: "two-",
771771
Resolution: 12,
772772
AutoCalculateImportedMatrices: true,
@@ -7925,6 +7925,162 @@ var Two = (() => {
79257925
return xhr2;
79267926
}
79277927

7928+
// src/effects/image.js
7929+
var _Image = class extends Rectangle {
7930+
_flagTexture = false;
7931+
_flagMode = false;
7932+
_texture = null;
7933+
_mode = "fill";
7934+
constructor(path, ox, oy, width, height, mode) {
7935+
super(ox, oy, width || 1, height || 1);
7936+
for (let prop in proto23) {
7937+
Object.defineProperty(this, prop, proto23[prop]);
7938+
}
7939+
this.noStroke();
7940+
this.noFill();
7941+
if (path instanceof Texture) {
7942+
this.texture = path;
7943+
} else if (typeof path === "string") {
7944+
this.texture = new Texture(path);
7945+
}
7946+
if (typeof mode === "string") {
7947+
this.mode = mode;
7948+
}
7949+
this._update();
7950+
}
7951+
static fromObject(obj) {
7952+
const image = new _Image().copy(obj);
7953+
if ("id" in obj) {
7954+
image.id = obj.id;
7955+
}
7956+
return image;
7957+
}
7958+
copy(image) {
7959+
super.copy.call(this, image);
7960+
for (let i = 0; i < _Image.Properties.length; i++) {
7961+
const k = _Image.Properties[i];
7962+
if (k in image) {
7963+
this[k] = image[k];
7964+
}
7965+
}
7966+
return this;
7967+
}
7968+
clone(parent) {
7969+
const clone = new _Image(
7970+
this.texture,
7971+
this.translation.x,
7972+
this.translation.y,
7973+
this.width,
7974+
this.height
7975+
);
7976+
if (parent) {
7977+
parent.add(clone);
7978+
}
7979+
return clone;
7980+
}
7981+
toObject() {
7982+
const object = super.toObject.call(this);
7983+
object.texture = this.texture.toObject();
7984+
object.mode = this.mode;
7985+
return object;
7986+
}
7987+
dispose() {
7988+
super.dispose();
7989+
if (this._texture && typeof this._texture.dispose === "function") {
7990+
this._texture.dispose();
7991+
} else if (this._texture && typeof this._texture.unbind === "function") {
7992+
this._texture.unbind();
7993+
}
7994+
return this;
7995+
}
7996+
_update() {
7997+
const effect = this._texture;
7998+
if (effect) {
7999+
if (this._flagTexture) {
8000+
this.fill = effect;
8001+
}
8002+
if (effect.loaded) {
8003+
const iw = effect.image.width;
8004+
const ih = effect.image.height;
8005+
const rw = this.width;
8006+
const rh = this.height;
8007+
const scaleX = rw / iw;
8008+
const scaleY = rh / ih;
8009+
switch (this._mode) {
8010+
case _Image.fill: {
8011+
const scale = Math.max(scaleX, scaleY);
8012+
effect.scale = scale;
8013+
effect.offset.x = 0;
8014+
effect.offset.y = 0;
8015+
effect.repeat = "repeat";
8016+
break;
8017+
}
8018+
case _Image.fit: {
8019+
const scale = Math.min(scaleX, scaleY);
8020+
effect.scale = scale;
8021+
effect.offset.x = 0;
8022+
effect.offset.y = 0;
8023+
effect.repeat = "no-repeat";
8024+
break;
8025+
}
8026+
case _Image.crop: {
8027+
break;
8028+
}
8029+
case _Image.tile: {
8030+
effect.offset.x = (iw - rw) / 2;
8031+
effect.offset.y = (ih - rh) / 2;
8032+
effect.repeat = "repeat";
8033+
break;
8034+
}
8035+
case _Image.stretch:
8036+
default: {
8037+
effect.scale = new Vector(scaleX, scaleY);
8038+
effect.offset.x = 0;
8039+
effect.offset.y = 0;
8040+
effect.repeat = "repeat";
8041+
}
8042+
}
8043+
}
8044+
}
8045+
super._update.call(this);
8046+
return this;
8047+
}
8048+
flagReset() {
8049+
super.flagReset.call(this);
8050+
this._flagTexture = this._flagMode = false;
8051+
return this;
8052+
}
8053+
};
8054+
var Image = _Image;
8055+
__publicField(Image, "fill", "fill");
8056+
__publicField(Image, "fit", "fit");
8057+
__publicField(Image, "crop", "crop");
8058+
__publicField(Image, "tile", "tile");
8059+
__publicField(Image, "stretch", "stretch");
8060+
__publicField(Image, "Properties", ["texture", "mode"]);
8061+
var proto23 = {
8062+
texture: {
8063+
enumerable: true,
8064+
get: function() {
8065+
return this._texture;
8066+
},
8067+
set: function(v) {
8068+
this._texture = v;
8069+
this._flagTexture = true;
8070+
}
8071+
},
8072+
mode: {
8073+
enumerable: true,
8074+
get: function() {
8075+
return this._mode;
8076+
},
8077+
set: function(v) {
8078+
this._mode = v;
8079+
this._flagMode = true;
8080+
}
8081+
}
8082+
};
8083+
79288084
// src/effects/image-sequence.js
79298085
var _ImageSequence = class extends Rectangle {
79308086
_flagTextures = false;
@@ -7943,8 +8099,8 @@ var Two = (() => {
79438099
_origin = null;
79448100
constructor(paths, ox, oy, frameRate) {
79458101
super(ox, oy, 0, 0);
7946-
for (let prop in proto23) {
7947-
Object.defineProperty(this, prop, proto23[prop]);
8102+
for (let prop in proto24) {
8103+
Object.defineProperty(this, prop, proto24[prop]);
79488104
}
79498105
this._renderer.flagTextures = FlagTextures.bind(this);
79508106
this._renderer.bindTextures = BindTextures.bind(this);
@@ -8142,7 +8298,7 @@ var Two = (() => {
81428298
"loop"
81438299
]);
81448300
__publicField(ImageSequence, "DefaultFrameRate", 30);
8145-
var proto23 = {
8301+
var proto24 = {
81468302
frameRate: {
81478303
enumerable: true,
81488304
get: function() {
@@ -9895,6 +10051,11 @@ var Two = (() => {
989510051
changed.width *= this._scale;
989610052
changed.height *= this._scale;
989710053
}
10054+
if (/no-repeat/i.test(this._repeat)) {
10055+
styles.preserveAspectRatio = "xMidYMid";
10056+
} else {
10057+
styles.preserveAspectRatio = "none";
10058+
}
989810059
styles.width = changed.width;
989910060
styles.height = changed.height;
990010061
}
@@ -11804,6 +11965,11 @@ var Two = (() => {
1180411965
this.add(sprite);
1180511966
return sprite;
1180611967
}
11968+
makeImage(pathOrTexture, x, y, width, height, mode) {
11969+
const image = new Image(pathOrTexture, x, y, width, height, mode);
11970+
this.add(image);
11971+
return image;
11972+
}
1180711973
makeImageSequence(pathsOrTextures, x, y, frameRate, autostart) {
1180811974
const imageSequence = new ImageSequence(pathsOrTextures, x, y, frameRate);
1180911975
if (autostart) {
@@ -11886,6 +12052,7 @@ var Two = (() => {
1188612052
__publicField(Two, "Text", Text);
1188712053
__publicField(Two, "Vector", Vector);
1188812054
__publicField(Two, "Gradient", Gradient);
12055+
__publicField(Two, "Image", Image);
1188912056
__publicField(Two, "ImageSequence", ImageSequence);
1189012057
__publicField(Two, "LinearGradient", LinearGradient);
1189112058
__publicField(Two, "RadialGradient", RadialGradient);

build/two.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)