Well, it's been two years since I posted anything. Hello again. It's the same schick, I'm slowly learning rust and need somewhere to collect my thoughts, so here we are. I thought I'd stick with the last post's theme, image manipulation. I won't really explain what image kernels are - just think of them as a filter that we apply to an image to sharpen, blur, edge detection, etc. I'll obviously explain how they are applied because that's part of the code, but if you want a visual guide to how they work, Victor Powell is where you'll want to start. Before we start, here's what the program does do my avatar. This time will be a little more complex, I want my program to:
Imports in main.rs
Ah, image my old friend. You have changed, you have stayed the same, I still love you. Anyway yes, we are still using image because it'll still the best library for general image loading, manipulation, and saving. We will be using a DynamicImage for all of our dirty work, but most of its cool pixel things are hidden inside GenericImage(View) so we'll import those as well. The next two are just to shorten some code later on. Simple stuff. Onto the meat of the program. apply_kernel functions in main.rs
Here's where the magic happens. Firstly, the parameters are the kernel (representing a 3x3 array) as a slice, and the mutable reference to the image we will edit. Then we take the width & height of the image and copy the image; because our function will be destructive to the input image, and we will need the unaltered pixels for our algorithm. You could also keep the input image unaltered and return a new image, but that's a design choice. For each pixel(x, y) in the image (excluding the edges... edge cases man, the bane of my existence), we take all the surrounding pixels and copy them into subpixels. Side note, I was planning on doing just referencing them, but it turns out [u8; 4] (the internal type of Rgba) is twice as small as &Rgba, at least on my machine. Because 4 u8s is 32 bits (4 bytes), while on my x64 Mac, a reference is 64 bits. the more you know. Anyway, we make a mutable [f32; 4] container called total to hold our new pixel which we will put into the input image. We do this because the filter can both add/subtract larger numbers than a u8 can hold, so we will convert that later to u8. Then, foreach of our subpixels - we use an enumeration so we have access to the index - we add the each channel (red, green, blue, alpha). The zeroth subpixel is multiplied with the corresponding kernel. So kernel[n] * subpixels[n].data[channel] for n => 0..8 gets added to the total[channel]. This "averages" or applies the kernel to the pixel in the original image. Now... this part I'm not too sure about. f32tou8... Firstly, a terrible name for a what is essentially a conversion function. It turns f32 into u8. I know what you are wondering. "William you dumbass, there's the as keyword made just for that, you literally use it in f32tou8 too" and you'd be right. However there's one small problem with the prebuild as. It's too good. Lemme give you an example. What do you think 0f32 as u8 equals? Yep. 0u8. Perfect. -1f32 as u8? If you said 255u8 you're smarter than me, I thought it would be 0u8. 256f32 as u8? If you said 0u8, you're still smarter than me. Damn edge cases again. It bloody handles overflowing like a boss. This is cool, but we REALLY don't want that, because if a pixel value we get is -128, it should be as black as my lungs, gray(128). If there's a better way to do this, please let me know. Then it's a simple matter of mapping our total: [f32; 4] to a Vec<32> using our function, and putting that pixel into the input image. This is why we needed to copy it, because the next loop would have taken that edited pixel instead of the original. That's it, the function finishes and what's left is a sharpened image. Onto main. main in main.rs
We ask the user for the image path, use my readln function from a few posts ago and hello... What is that question mark. Now, when I came back to rust after my hiatus, I found out the main now supports a return type, std::io::Result<()>, which makes error handling with IO super easy, because you can write ? as shorthand for .unwrap(). My readln also returns this type, hence the ?. We then load the image, and gracefully handle when something goes terribly wrong. And hello, the actual kernel, which I copied of wikipedia (thanks wikipedia). We use out apply_kernal function, then make a new path using format!, which is like println!, but it returns the string rather than printing it. Good stuff. We save the image using the new path, then tell the user we have finished. Lastly, because our main now returns a Result, we need to return an empty Ok result. Now, there's a problem with my problem. God damn edge cases.
Now currently, the when apply_images finishes, the image will have the same pixels around the border. This is fine when we are sharpening the image. However, if you are doing edge detection, this looks awful and does not work. There are ways to handle the edges (make the image 2px smaller, average the pixels new the border, apply only a subsection of the kernel to the edges/corners) but truly I couldn't be bothered. And that's it. This was a long one, but I enjoyed playing around with images and rust again. Thanks for reading.
0 Comments
Today I'm going to attempt the impossible, the crazy, the daring and bold - I'm going to... Grayscale an image. I know, it's going to be intense. There's not much else to say except for the requirements of this project. I want my program to:
Should be easy enough. main.rs
Alright let's explain some things.
It's my first time using extern crate, so let's explain that. In the file where I ran cargo run, there is a file called cargo.toml. It contains some pre-filled out information about the author, and name of the package. There is also the line [dependencies], underneath which you can put any crates from crates.io to download for your project. It's very useful! Because we can just put the new line image = "0.10.4", and extern crate image in main.rs, to start using this. Now the main function. First, I find what file path the executable is running in (we'll assume the user knows the image should be in the same folder). It asks the user for the name of the image, reads the input then pushes it onto the path (automatically adding the os file separator). Then it checks if the image is there, if it is then set it to img. If not, print the error and exit(). The dimensions are then read from the image, then a new ImageBuffer is created by using the dimensions and a closure. A closure is a function that is passed as a parameter for another function (I think). This one has the inputs |x, y|, which will be iterated though, for each x and y. The closure expects a pixel for each, so we return our old images pixel at x and y, which is changed to_luma() (which is grayscaled, it only has luminosity). This creates the grayscaled image gs_img. Then it makes a reference to a file "output.png" (this one will be in the working directory automatically). Then it saves gs_img to it. There, easy. Notes:
Edits:
Oh Rust. When I first saw the macro println!() I smiled. No longer must I write out the drawn out, dreadful, painful, hand cramping System.out.println() of Java or std::cout << [] << std::endl of C++. I was... happy. But there's a problem in Rust. Glaring, haunting, causing pain and suffering. Why isn't there a macro for reading io? I looked at creating custom macros but they looked even more daunting, so I might try that later on. So, my goal now is to make a function that can robustly read a line from io, and to make it readable. I'll make a "Hello Bot" (Type in your name, it says hello back) to test it. This is my attempt: main.rs
Let us walk through it.
For the first [now the second] time, we find ourselves using the use command. This tells the compiler that we want to use something that isn't directly available to us, for this case, stdin and Error from io. The function readln() is where the magic happens. First, I declare a mutable String called buffer, the next function call borrows a reference of it as a argument, and that's how you get the information back. Moving on. The match statement is for the return value of the function stdin().read_line(), which is a Result<usize, io::Error>. Why does it return the size of the string rather than the string itself and save itself from the &mut buffer parameter? No clue, it's kind of annoying. After a successful read, the buffer will probably have a "new line" character at the end, so we need to tuncate() the buffer by it's length - 1, then return the result Ok(buffer). After a unsuccessful read, the error will be returned (Just like read_line()), allowing the user of the function to choose what to do if it fails to read a line. And the main function isn't very important this time. It contains a simple request for a name, and a new call unwrap() which removes the Ok() or Err() wrapper, returning the inside value. You can do a match statement on readln() for true error handling. Again, thanks to Lokathor for the help. Thanks for reading - Gutrix. Edit history:
Source. Try the problem first if you want. /// Edited Below Ah. The Fizzbuzz problem. This question has struck a questionable and irrational fear in many programmers by its prevalence in job interview's questionnaires. It's simple question which is designed to make you overthink the problem. It has four simple requirements :
When I wrote the program in Rust (I've written it before in Java without much worries), I wanted it to be easily read and efficient. So, here is my attempt: Edited: Special thanks to reddit user l-arkham, for suggesting the usage of use FizzBuzzOptions::*; to bring the varients of the enumeration into a global scope, which allows for cleaner, more readable code. main.rs
Let us walk through it.
First thing is the enumeration FizzBuzzOptions, which (I think) helps the programmer handle the concepts of the requirements with a little abstraction. FizzBuzzOptions contains four enums, one for each requirement (FizzBuzz, Buzz, Fizz and Neither). Next is the function matching, which returns the appropriate FizzBuzzOption[s]. This is were the ordering of the if statements matters, as well as the fourth requirement's weird wording "multiples of both three and five", which means a multiple of fifteen. Whether x % 15 == 0 is true must be checked first before both x % 5 == 0 and x % 3 == 0. Lastly the main function, which contains the loop for x in 1..101 (Note: Rust lists .. are inclusive on the left, and exclusive on the right, so 1..5 means [1, 2, 3, 4]). The match statement calls matching for each x and compares the return to the list of options, picks the FizzBuzzOption[s] that matches (funny that), and executes the println!(). Nice and simple, and very easy to read. Some notes :
That's it. Thanks for reading. - Gutrix This is my first post so I'm going to set up some things here.
By default, this is the source code (which is in the file [project_name]/src/main.rs) : Riveting, I know. There are three important things to point out.
There, I think that's it. |
ArchivesCategories |