Πώς να εμφανίσετε μια εικόνα σε καμβά HTML5

Εντάξει, λοιπόν εδώ είναι μια ερώτηση: "Γιατί χρειαζόμαστε ένα άρθρο για αυτό, Nash;"

Λοιπόν, πάρτε μια θέση.

Οχι περίμενε! Πρώτα, ρίξτε μια ματιά σε αυτό.

Ακριβώς. Τι ήταν αυτό?

drawImageείναι η μέθοδος που χρησιμοποιείται για την εμφάνιση ή "σχεδίαση" μιας εικόνας canvas. Ίσως ή ίσως να μην γνωρίζετε ήδη ότι δεν είναι τόσο απλό όσο απλώς μεταδίδετε το URI της εικόνας σε αυτό. drawImageδέχεται το πολύ 9 παραμέτρους. Πηγαίνουν κάπως έτσι, έτοιμοι; Κρατήστε την αναπνοή σας…

(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

Εκπνοή.

Βρήκα την τεκμηρίωση για drawImageλίγο συγκεχυμένο και σκληρό. Απλώς η τεκμηρίωση, ναι. Η ιδέα και ο τρόπος λειτουργίας του API είναι ιδανικός για όλες τις ανάγκες που υποτίθεται ότι εξυπηρετεί.

Θα εξετάσουμε τις παραμέτρους που αναφέρονται παραπάνω μία προς μία, με τρόπο που θα έχει νόημα για εσάς. Εάν σε οποιοδήποτε σημείο του άρθρου βρεθείς «ήθελα απλώς να σχεδιάσω μια εικόνα στον καμβά μου, αγαπητέ Nash. Γιατί βάζω το μυαλό μου στο κουδούνισμα; », θα είναι μια κατανοητή απογοήτευση

Ο τρόπος drawImageλειτουργίας φαίνεται περίπλοκος σε κάποιο βαθμό, αλλά αυτή η πολυπλοκότητα καθιστά drawImageεξαιρετικά ισχυρή και χρήσιμη - όπως θα δούμε παραδείγματα στο τέλος. Επιπλέον, η πολυπλοκότητα είναι ακριβώς στην επιφάνεια: μόλις καταλάβετε ολόκληρη την εικόνα είναι μια κατηφορική ποδηλασία σε έναν επαρχιακό δρόμο κάπου στην Ευρώπη.

Μέχρι το τέλος αυτού του άρθρου θα μπορείτε να απεικονίσετε πώς drawImageθα τραβήξετε οποιαδήποτε δεδομένη εικόνα canvas, κοιτάζοντας μόνο τις τιμές των 9 παραμέτρων. Ακούγεται σαν μια υπερδύναμη που ίσως θέλετε να έχετε; Εντάξει λοιπόν, ας βουτήξουμε αμέσως!

Φόρτωση εικόνας σε καμβά

Ας ξεκινήσουμε απλοί με μια εικόνα και ένα HTML5 canvas.

Δείτε πώς φαίνεται ο κατάλογός μας

Μέσα στο index.htmlαρχείο μας έχουμε δημιουργήσει ένα νέο στοιχείο καμβά έτσι.

Στόχος μας είναι να τραβήξουμε την cat.jpgεικόνα και να την βάλουμε στον καμβά ( #my-canvas). Και όπως είπα ήδη, δεν είναι τόσο εύκολο! Διαφορετικά δεν θα γράφω αυτό το άρθρο, με νιώθετε; Καλός.

Αρχικά, ας στοχεύσουμε το canvasστοιχείο χρησιμοποιώντας JavaScript και πάρουμε το περιβάλλον του.

const myCanvas = document.getElementById('my-canvas'); const myContext = myCanvas.getContext('2d');

Πρέπει myContextνα αλληλεπιδράσουμε με το canvasστοιχείο. Είναι σαν, εάν canvasείναι ένα κενό φύλλο χαρτιού, το πλαίσιο του καμβά είναι το στυλό. Διαισθητικά, θα πείτε στο στυλό σας να σχεδιάσει κάτι σε ένα κενό φύλλο χαρτιού, και όχι μόνο να φωνάξετε στο χαρτί για να σχεδιάσετε κάτι από μόνος του σωστά;

Υπάρχουν πολλά πράγματα που μπορείτε να κάνετε context. Μπορείτε να σχεδιάσετε ένα ορθογώνιο ή μια έλλειψη ή μια γραμμή ή μια… εικόνα . Επίσης, παρατηρήστε ότι το πλαίσιο myContextσυνδέεται σιωπηρά με myCanvas. Μπορείτε να έχετε πολλά canvases και να καλέσετε getContext()καθένα από αυτά για να αποκτήσετε ένα νέο πλαίσιο / στυλό για κάθε ένα. Στην περίπτωσή μας έχουμε να κάνουμε με έναν μόνο καμβά ( myCanvas) και ένα μόνο πλαίσιο ( myContext).

Εντάξει, χωρίς αυτό, μπορούμε τελικά να αρχίσουμε να βρέχουμε τα πόδια μας drawImage.

Για μια ανανέωση, ακολουθούν οι 9 παράμετροι που drawImageδέχονται.

(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

Θα ξεκινήσουμε με την πρώτη παράμετρο, image. Ας γράψουμε πρώτα κάτι που δεν λειτουργεί.

context.drawImage('./cat.jpg', 0, 0);

Βλέπετε τα δύο μηδενικά στο τέλος; Καλός. Αυτό δεν είναι το μέρος του άρθρου στο οποίο υποτίθεται ότι καταλαβαίνετε τι είναι εκεί. Αγνοήστε τα προς το παρόν, απλώς κρατήστε στο πίσω μέρος του κεφαλιού σας ότι ο Νας έγραψε 2 μηδενικά και δεν τους εξήγησε. Δεν με πειράζει.

Τώρα παρατηρήστε ...('./cat.jpg',..στη γραμμή κώδικα παραπάνω. Φαίνεται ότι είναι ένα απόλυτα σωστό URI, έτσι δεν είναι; Και είναι… buuuut, εάν ενεργοποιήσετε index.htmlένα πρόγραμμα περιήγησης, θα δείτε ένα μακρύ μακρύ μήνυμα σφάλματος ίδιο με αυτό που φαίνεται παρακάτω.

ERROR: The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)

*χαψιά*

Το σφάλμα μας λέει ότι χρειάζεται ένα στοιχείο εικόνας και όχι μόνο ένα URI στην εικόνα. Για να το ξεπεράσουμε αυτό, μπορούμε να κάνουμε.

const canvas = document.getElementById('canvas'); const context = canvas.getContext('2d'); const img = new Image(); img.src = './cat.jpg'; img.onload = () => { context.drawImage(img, 0, 0); };

Αυτό είναι κάτι που δεν περίμενατε; Ο καμβάς χρειάζεται μια προφορτωμένη εικόνα για να το σχεδιάσει / εμφανίσει από μόνο του. Παρεμπιπτόντως, δεν χρειάζεται να δείχνετε περιφρόνηση στον καμβά. Έχει το λόγο του, είναι ακριβώς όπως οι υπόλοιποι από εμάς. Τελικά θα δούμε ποιοι είναι αυτοί οι λόγοι και ίσως τότε θα μπορείτε να συμπαθείτε.

Για να ανακεφαλαιώσουμε:

drawImageζητά 9 παραμέτρους, η πρώτη από τις οποίες είναι image. Κοιτάξαμε και καταλάβαμε ότι canvasαπαιτείται μια προεγκατεστημένη εικόνα για να σχεδιάσουμε και όχι μόνο ένα URI στην εικόνα. Γιατί το χρειάζεται αυτό; Θα γίνει σαφές καθώς διαβάζετε.

Τώρα ήρθε η ώρα για τις 8 παραμέτρους. Πιάστε τα κολάρα σας! Θα σας μάθω πρώτα κάποια επεξεργασία γραφικών!

Πώς να περικόψετε μια εικόνα

Κάθε πρόγραμμα επεξεργασίας γραφικών, ακόμη και το πιο βασικό, συνοδεύεται από τη δυνατότητα περικοπής. Είναι αρκετά απλό: ανοίξτε μια εικόνα> επιλέξτε την περιοχή που θέλετε ορατή> περικοπή επιτυχίας. Και έτσι, η γυμνή κοιλιά μπύρας αυτού του ενοχλητικού μυρίζοντας γέρου είναι έξω. Ομοφυλόφιλος!

Τεχνολογία! Αποθήκευση Instagrams ανθρώπων από τότε που υπήρχε το Instagram.

Ας κάνουμε ένα βήμα πίσω και να σταματήσουμε εδώ, εδώ.

Ας σημειώσουμε μερικά σημεία σε αυτό.

"Περιμένετε ένα δευτερόλεπτο! sx, sy, sWidthΚαι sHeight; Τους έχω ξαναδεί! "

Ναι, πριν από ένα λεπτό! Αυτό μας οδηγεί στο πιο σάρκα του άρθρου.

Εμφάνιση εικόνας σε καμβά, Βήμα 1: Επιλογή

Η πρώτη εργασία που drawImageεκτελείται (πίσω από τα παρασκήνια) είναι ότι επιλέγει ένα τμήμα της εικόνας με βάση τις τέσσερις sπαραμέτρους ( sx, sy, sWidth, sHeight). Μπορείτε να πείτε ότι s σε όλες τις παραμέτρους σημαίνει "select"

Δείτε πώς πηγαίνει. sxκαι syσημειώστε το σημείο της εικόνας από το οποίο θα ξεκινήσει η επιλογή, ή με άλλα λόγια τις συντεταγμένες της επάνω αριστεράς γωνίας του ορθογωνίου επιλογής. sWidthκαι sHeightστη συνέχεια, είναι το πλάτος και το ύψος του ορθογωνίου επιλογής αντίστοιχα. Μπορείτε να πραγματοποιήσετε κύλιση μέχρι την τελευταία εικόνα για να λάβετε μια σαφέστερη εικόνα αυτού που προσπαθώ να εξηγήσω.

«Αλλά γιατί η επιλογή Nash; Δεν μπορεί απλώς να εμφανίσει ολόκληρη την εικόνα; " Πλησιάζουμε σε όλες τις απαντήσεις σας, υπομονή.

Just know that the first step drawImage performs after receiving a proper image is it selects a portion/area of the image based on the s parameters (sx, sy, sWidth, sHeight) you provide.

Remember that you don’t always have to select a small portion of the image, you can select the entire image if you want to. In that case sx and sy will have values 0 and 0 respectively and sWidth, sHeight will be the same as the image’s width and height.

Also, negative values are welcome for sx and sy. The values of sx and sy are relative to the origin of the image on the top left.

Once drawImage has selected the area of image you asked it to – and we’ll see soon why selecting an area of the image helps – the next step is to draw the selected portion of the image on the canvas.

“Originally” s and d in the official documentation stand for ‘source’ and ‘destination’. But, just between us, let’s call it ‘select’ and ‘draw’. It makes much more sense this way, doesn’t it?

Again. selection is done, the next step is to draw.

Displaying an image on canvas, Step 2: Drawing

To draw the selected portion of the image, we again need four parameters.

  1. x Coordinate of where to start drawing on the canvas. ( dx )
  2. y Coordinate of where to start drawing on the canvas. ( dy )
  3. How wide to draw the image. ( dWidth )
  4. How high/tall to draw the image. ( dHeight )

The values of dx and dy will be relative to the origin of the canvas.

There’s a very important but subtle detail to notice here. dWidth and dHeight are in no way tied to sWidth and sHeight. They are independent values. Why do you need to know that? Well, because if you don’t choose values of the width and height of ‘draw’ carefully you will end up with a stretched or squashed image, like this.

So if that’s something you’re not looking for (which I hope you’re not), make sure to maintain the aspect ratio. Or so to say sWidth divided by sHeight should be equal to dWidth divided by dHeight. That was a small little disclaimer, you’re the ruler of your own world and free to choose whatever values you like.

The whole process of displaying/drawing an image on canvas can thus be summarised in just two steps: Selection and Drawing.

Awesome! Not so complicated after all is it?

Now at this point, we’re done with all the theory. In rest of the article that follows we’ll bake the batter of knowledge spread around your head with a fun and practical example and you’ll be good to go. But, before we do that, let’s talk about one last important thing concerning drawImage.

The default values

Remember my lecture on “hey keep the aspect ratio and be careful don’t take chocolates from strangers…”? Well, as it turns out, you can omit certain values and not worry about the aspect ratio at all. As far as taking chocolates from strangers go, again — you’re the ruler of your own world.

Here’s one way to use the method.

drawImage(image, dx, dy)

That is all! In this case, you’re telling drawImage only the location on canvas where to start the drawing. The rest, sx, sy, sWidth, sHeight, dWidth and dHeight are taken care of automagically. The method selects the entire image (sx = 0, sy = 0, sWidth = image's width, sHeight = images' height) and starts drawing on canvas at (dx, dy) with dWidth and dHeight same as sWidth(image’s width), sHeight(image’s height) .

Remember the two zeroes that I didn’t explain? That is where the two zeroes came from.

Yet another way to use the method is,

drawImage(image, dx, dy, dWidth, dHeight)

In this form sx, sy, sWidth and sHeight are taken care of, and the method automatically selects the entire image and leaves it up to you to choose where and how large of an image to draw.

Pretty cool! isn’t it?

If I can have your attention for another two minutes I’d like to tell you why selection and drawing are two separate operations. And how it is helpful.

Do I have your attention? Great!

So here.

Heard of sprites before? You see, sprites are a computer graphics concept where a graphic may be moved on-screen and otherwise manipulated as a single entity.

…?

I copied this definition from Google to sound suave.

Alright alright. Remember Mario?

Good.

Let’s do something fun.

Animating Mario with drawImage

You see, when Mario moves forward/backward or in any other direction, it appears as if he is walking. His position changes but also there is an accompanying animation of his legs and hands moving.

How do they do that? Do they show different images of Mario in succession, like a flipbook and it appears as if he’s moving?

Well, 50% yes. Imagine how resource intensive storing and loading a huge set of images describing every frame of animation in our program (or game) would be. Instead, there’s a single image and all the positions are laid out in a grid. Like the one shown below.

To execute an animation, instead of loading a new image every millisecond, a portion of the same image is shown through a viewport just at different positions. Clever isn’t it?

So yes, it’s sorta like a flipbook, a clever flipbook actually.

Now if you could just stretch a little and pop your knuckles I would like us to recreate Mario’s walking animation. We’ll use the sprite shown above and everything we have learnt about drawImage so far.

Ready? Here we go!

Let’s take another look at our sprite and try to figure the grid dimensions that it has been laid out on.

All that we have done here is imagined a grid over the sprite. Notice that the entire grid is made up of cells of equal dimensions (32 x 39). But it’s still just one image, remember that.

Great! Now let’s get to writing some code. We’ll start in the usual way by first creating a canvas element, grabbing it and its context in JavaScript, and then loading our Mario spritesheet.

// index.js const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const img = new Image(); img.src = './mario.png'; img.onload = () => { ctx.drawImage(img, 0, 0); }; 
// style.css canvas { /*Add a border around canvas for legibility*/ border: 1px solid grey; }

The above code will result in the following.

Woah-kay! We’ve got the image showing! What’s happening really?

Here, we’re using the form of drawImagedrawImage(image, sx, sy)–where the whole image is selected and drawn on the canvas as it is.

What we want to do, first of all, is select just one cell of the grid and draw just that single cell. Let’s start out by first making tweaks to our code that selects the first cell in the third row, the one in which Mario is standing facing east. We’ll figure how to animate once we have that done. Sounds good? Lovely!

Let’s make the necessary changes to our code.

const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); 
// Mario variables const MARIO_WIDTH = 32; const MARIO_HEIGHT = 39; 
const mario = new Image(); mario.src = './mario.png'; mario.onload = () => { ctx.drawImage( // Image mario, // ---- Selection ---- 0, // sx MARIO_HEIGHT * 2, // sy MARIO_WIDTH, // sWidth MARIO_HEIGHT, // sHeight // ---- Drawing ---- 0, // dx 0, // dy MARIO_WIDTH, // dWidth MARIO_HEIGHT // dHeight ); };

First off, notice the two variables MARIO_WIDTH and MARIO_HEIGHT. They are the dimensions of the grid cell, that’s all they are. We defined them to make it easier for us to traverse the grid using just multiples of each of those constants. Makes sense?

Good.

Next, in the // Selection block we defined the area of the image we want to select, in the // Drawing section we defined the width and height and the position from where to start drawing on the canvas… aaand just like that we managed to draw just one cell of the entire imaginary grid.

Pretty simple, just selection and drawing. Now at this point I’d like to digress into an older topic about aspect ratio. “Nash! again? ugghh” I know I know. But it’s cool! Look!

If I change the values of dWidth or dHeight or both of them, look at how the image stretches and squashes.

... ctx.drawImage( // Image mario, // ---- Selection ---- 0, // sx MARIO_HEIGHT * 2, // sy MARIO_WIDTH, // sWidth MARIO_HEIGHT, // sHeight // ---- Drawing ---- 0, // dx 0, // dy MARIO_WIDTH * 2, // dWidth MARIO_HEIGHT * 1.5 // dHeight ); ...

Hah! See! That’s why I was advising you to maintain the aspect ratio and that the values of selection and drawing have no real interconnection.

Okay, back to what we were doing.

So now we have Mario in the canvas, small and little. We need to animate it, or in other words show different frames at the same location in succession and make the illusion of movement happen. Was I too specific? Heck yeah!

We can do that by selecting the grid cells we want to draw in succession. We just need to change the value of sx by the multiples of MARIO_WIDTH.

Now doing this will require the use of requestAnimationFrame and I have been explaining that in a streak in this article and this article.

As a small challenge why don’t you go ahead and try accomplishing this on your own? In any case, you can check out this Codepen where I have Mario running like this. The pen has enough comments to help you understand the tiny bit of high school math that’s being used to make the animation happen.

Cute little thing!

Και με αυτό, τελειώσαμε με μια πολύ περιεκτική εξήγηση του drawImage. Ελπίζω να σας άρεσε.

Εάν το έχετε φτάσει μέχρι τώρα, τι θα λέγατε για να μου τραβήξετε κάποια σχόλια ή # goodvibes στο Twitter;

Αυτό το άρθρο δημοσιεύθηκε αρχικά στο www.nashvail.me.

Σας είπα για τη νέα μου ιστοσελίδα; Και σας είπα ότι έχει και ένα ενημερωτικό δελτίο; Θα ήθελα πολύ αν εγγραφείτε, ώστε να μπορώ να σας ειδοποιώ κάθε φορά που δημοσιεύω κάτι νέο ή βάζω κάτι νέο προς πώληση στο κατάστημά μου. Θα συνεχίσω να δημοσιεύω άρθρα στο Medium, αλλά θα υπάρχει ένα κενό δύο εβδομάδων μεταξύ του όταν ανεβαίνει για πρώτη φορά στον ιστότοπό μου και όταν εμφανίζεται εδώ.

Σας ευχαριστώ πολύ για την ανάγνωση και σας ευχαριστώ πολύ για την υποστήριξή σας. Να έχεις ένα καλό! :)