How to load local html to headless chrome - go

I have local html files which I need to render them and get their screenshot.
I could not find any solution to load html codes in chromedp
Is that possible?

Yes, it is.
In chromedp documentation there is a nice example https://github.com/chromedp/examples/blob/master/screenshot/main.go. Only difference is that instead of using "https://..." in urlstring you will use "file:///<absolute_path_to_your_file>" .
Example of code, which I mostly took from the example link and used to make screenshot of html stored on my local system:
package main
import (
"context"
"io/ioutil"
"log"
"math"
"github.com/chromedp/cdproto/emulation"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp"
)
func main() {
// create context
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
//if you want to use html from your local filesystem use file:/// + absolute path to your html file
url := "file:///home/oktogen/Documents/Notebooks/2020/May/AnalysisJobs/FlaskApp/templates/index.html"
// capture screenshot of an element
var buf []byte
// capture entire browser viewport, returning png with quality=90
if err := chromedp.Run(ctx, fullScreenshot(url, 90, &buf)); err != nil {
log.Fatal(err)
}
if err := ioutil.WriteFile("fullScreenshot.png", buf, 0644); err != nil {
log.Fatal(err)
}
}
// fullScreenshot takes a screenshot of the entire browser viewport.
//
// Liberally copied from puppeteer's source.
//
// Note: this will override the viewport emulation settings.
func fullScreenshot(urlstr string, quality int64, res *[]byte) chromedp.Tasks {
return chromedp.Tasks{
chromedp.Navigate(urlstr),
chromedp.ActionFunc(func(ctx context.Context) error {
// get layout metrics
_, _, contentSize, err := page.GetLayoutMetrics().Do(ctx)
if err != nil {
return err
}
width, height := int64(math.Ceil(contentSize.Width)), int64(math.Ceil(contentSize.Height))
// force viewport emulation
err = emulation.SetDeviceMetricsOverride(width, height, 1, false).
WithScreenOrientation(&emulation.ScreenOrientation{
Type: emulation.OrientationTypePortraitPrimary,
Angle: 0,
}).
Do(ctx)
if err != nil {
return err
}
// capture screenshot
*res, err = page.CaptureScreenshot().
WithQuality(quality).
WithClip(&page.Viewport{
X: contentSize.X,
Y: contentSize.Y,
Width: contentSize.Width,
Height: contentSize.Height,
Scale: 1,
}).Do(ctx)
if err != nil {
return err
}
return nil
}),
}
}

Related

Reading Image in Go

func (sticky *Sticky) DrawImage(W, H int) (img *image, err error) {
myImage := image.NewRGBA(image.Rect(0, 0, 10, 25))
myImage.Pix[0] = 55 // 1st pixel red
myImage.Pix[1] = 155 // 1st pixel green
return myImage ,nil
}
I am creating an image. I want to read the existing Image and return in this Function. How I can I do that?
Something like this:
func getImageFromFilePath(filePath string) (image.Image, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer f.Close()
image, _, err := image.Decode(f)
return image, err
}
references
https://golang.org/pkg/image/#Decode
https://golang.org/pkg/os/#Open
try this:
package main
import (
"fmt"
"image"
"image/png"
"os"
)
func main() {
// Read image from file that already exists
existingImageFile, err := os.Open("test.png")
if err != nil {
// Handle error
}
defer existingImageFile.Close()
// Calling the generic image.Decode() will tell give us the data
// and type of image it is as a string. We expect "png"
imageData, imageType, err := image.Decode(existingImageFile)
if err != nil {
// Handle error
}
fmt.Println(imageData)
fmt.Println(imageType)
// We only need this because we already read from the file
// We have to reset the file pointer back to beginning
existingImageFile.Seek(0, 0)
// Alternatively, since we know it is a png already
// we can call png.Decode() directly
loadedImage, err := png.Decode(existingImageFile)
if err != nil {
// Handle error
}
fmt.Println(loadedImage)
}
references
https://www.devdungeon.com/content/working-images-go

How can I check for an element is present in the page using golang knq/chromedp

I am creating an app to using [chromedp][1]
How can I check for an element is present in the page?
I tried to use cdp.WaitVisible() but it didn't give me what I wanted.
I need this so I can make dictions if the application will do one thing or the other.
For this example, suppose I need to know if the search input is present or not
How can I do that?
[1]: https://github.com/knq/chromedp
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"time"
cdp "github.com/knq/chromedp"
cdptypes "github.com/knq/chromedp/cdp"
)
func main() {
var err error
// create context
ctxt, cancel := context.WithCancel(context.Background())
defer cancel()
// create chrome instance
c, err := cdp.New(ctxt, cdp.WithLog(log.Printf))
if err != nil {
log.Fatal(err)
}
// run task list
var site, res string
err = c.Run(ctxt, googleSearch("site:brank.as", "Easy Money Management", &site, &res))
if err != nil {
log.Fatal(err)
}
// shutdown chrome
err = c.Shutdown(ctxt)
if err != nil {
log.Fatal(err)
}
// wait for chrome to finish
err = c.Wait()
if err != nil {
log.Fatal(err)
}
log.Printf("saved screenshot of #testimonials from search result listing `%s` (%s)", res, site)
}
func googleSearch(q, text string, site, res *string) cdp.Tasks {
var buf []byte
sel := fmt.Sprintf(`//a[text()[contains(., '%s')]]`, text)
return cdp.Tasks{
cdp.Navigate(`https://www.google.com`),
cdp.Sleep(2 * time.Second),
cdp.WaitVisible(`#hplogo`, cdp.ByID),
cdp.SendKeys(`#lst-ib`, q+"\n", cdp.ByID),
cdp.WaitVisible(`#res`, cdp.ByID),
cdp.Text(sel, res),
cdp.Click(sel),
cdp.Sleep(2 * time.Second),
cdp.WaitVisible(`#footer`, cdp.ByQuery),
cdp.WaitNotVisible(`div.v-middle > div.la-ball-clip-rotate`, cdp.ByQuery),
cdp.Location(site),
cdp.Screenshot(`#testimonials`, &buf, cdp.ByID),
cdp.ActionFunc(func(context.Context, cdptypes.Handler) error {
return ioutil.WriteFile("testimonials.png", buf, 0644)
}),
}
}
Here is my answer.
The web page is www.google.co.in. The element used is lst-ib, Text box present on the page.
Navigate the page.
Wait until the element is visible.
Read the value of the element. This is first time page is being loaded so obviously value will be "".
Assume, you have modified the value of the element by typing in the text box. Now, if we try to read the value of the same element lst-ib then we should get the updated value.
My code is below,
package main
import (
"context"
"log"
cdp "github.com/knq/chromedp"
)
func main() {
var err error
// create context
ctxt, cancel := context.WithCancel(context.Background())
defer cancel()
// create chrome instance
c, err := cdp.New(ctxt)
if err != nil {
log.Fatal(err)
}
// run task list
var res, value, newValue string
err = c.Run(ctxt, text(&res, &value, &newValue))
if err != nil {
log.Fatal(err)
}
// shutdown chrome
err = c.Shutdown(ctxt)
if err != nil {
log.Fatal(err)
}
// wait for chrome to finish
err = c.Wait()
if err != nil {
log.Fatal(err)
}
if len(value) > 1 {
log.Println("Search Input is present.")
} else {
log.Println("Search Input is NOT present.")
}
log.Println("New updated value: ", newValue);
}
func text(res, value, newValue *string) cdp.Tasks {
return cdp.Tasks{
cdp.Navigate(`https://www.google.co.in`),
cdp.WaitVisible(`lst-ib`, cdp.ByID),
cdp.EvaluateAsDevTools("document.getElementById('lst-ib').value", value),
cdp.EvaluateAsDevTools("document.getElementById('lst-ib').value='Hello';document.getElementById('lst-ib').value", newValue),
}
}
To run code use go run <FileName>.go
I am getting following output which was expected:
$ go run main.go
2017/09/28 20:05:20 Search Input is NOT present.
2017/09/28 20:05:20 New updated value: Hello
NOTE:
First I checked with Google Chrome Developer Tools to get exact Javascripts for my need. It helps a lot.
I have added the screenshot of the Javascript I tried on Chrome Developer Tools.
I hope it helps you. :)

Looking for better way to save an in memory image to file

The goal of the code is to download an image, stick it to a larger parent image and save the result.
After quite a few failures I ended up with the following code that does work.
However, is there a better way than using bytes.Buffer and a writer to save the target image to a file / pass it to an httpResponse?
package main
import (
"image"
"image/draw"
"image/jpeg"
"os"
// "image/color"
// "io/ioutil"
// "fmt"
"bufio"
"bytes"
"log"
"net/http"
)
func main() {
// Fetch an image.
resp, err := http.Get("http://katiebrookekennels.com/wp-content/uploads/2014/10/dog-bone4.jpg")
if err != nil {
log.Panic(err)
}
defer resp.Body.Close()
// Keep an in memory copy.
myImage, err := jpeg.Decode(resp.Body)
if err != nil {
log.Panic(err)
}
// Prepare parent image where we want to position child image.
target := image.NewRGBA(image.Rect(0, 0, 800, 800))
// Draw white layer.
draw.Draw(target, target.Bounds(), image.White, image.ZP, draw.Src)
// Draw child image.
draw.Draw(target, myImage.Bounds(), myImage, image.Point{0, 0}, draw.Src)
// Encode to jpeg.
var imageBuf bytes.Buffer
err = jpeg.Encode(&imageBuf, target, nil)
if err != nil {
log.Panic(err)
}
// Write to file.
fo, err := os.Create("img.jpg")
if err != nil {
panic(err)
}
fw := bufio.NewWriter(fo)
fw.Write(imageBuf.Bytes())
}
jpeg.Encode() expects an io.Writer to write the encoded image to (in JPEG format). Both *os.File and http.ResponseWriter implement io.Writer too, so instead of a bytes.Buffer, you can directly pass a file or HTTP response writer too.
To save an image directly to a file:
f, err := os.Create("img.jpg")
if err != nil {
panic(err)
}
defer f.Close()
if err = jpeg.Encode(f, target, nil); err != nil {
log.Printf("failed to encode: %v", err)
}
To serve it as an image:
// w is of type http.ResponseWriter:
if err := jpeg.Encode(w, target, nil); err != nil {
log.Printf("failed to encode: %v", err)
}
You can see some examples in these questions (saving/loading JPEG and PNG images):
Draw a rectangle in Golang?
How to add a simple text label to an image in Go?
Change color of a single pixel - Golang image

Encoding an Image to JPEG in Go

I have a struct called SpriteImage which is defined like this:
type SpriteImage struct {
dimentions image.Point
lastImgPosition image.Point
sprite *image.NRGBA
}
In my flow, I first initiate a new such struct:
func NewSpriteImage(width, height int) SpriteImage {
c := color.RGBA{0xff, 0xff, 0xff, 0xff}
blankImage := imaging.New(width, height, c)
return SpriteImage{
dimentions: image.Point{X: width, Y: height},
lastImgPosition: image.Point{X: 0, Y: 0},
sprite: blankImage,
}
}
And then I add images to this SpriteImage like so:
func (s *SpriteImage) AddImage(img image.Image) error {
imgWidth := img.Bounds().Dx()
imgHeight := img.Bounds().Dy()
// Make sure new image will fit into the sprite.
if imgWidth != s.dimentions.X {
return fmt.Errorf("image width %d mismatch sprite width %d", imgWidth, s.dimentions.X)
}
spriteHeightLeft := s.dimentions.Y - s.lastImgPosition.Y
if imgHeight > spriteHeightLeft {
return fmt.Errorf("image height %d won't fit into sprite, sprite free space %d ", imgHeight, s.dimentions.Y)
}
// add image to sprite
s.sprite = imaging.Paste(s.sprite, img, s.lastImgPosition)
// update next image position within sprite
s.lastImgPosition = s.lastImgPosition.Add(image.Point{X: 0, Y: imgHeight})
return nil
}
Eventually, I want to take this SpriteImage and encode it as JPEG. But it doesn't seem to work. The native JPEG Encode function takes up an image, but I have an image.NRGBA. So I'm using github.com/disintegration/imaging lib like so:
func (s SpriteImage) GetBytes() ([]byte, error) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
if s.sprite == nil {
return nil, fmt.Errorf("sprite is nil")
}
if err := imaging.Encode(w, s.sprite, imaging.JPEG); err != nil {
return nil, err
}
return b.Bytes(), nil
}
However is seems that the bytes returned are not in fact JPEG. The native Go JPEG lib will not decode those bytes to a Go image struct. If I try to decode those bytes to image like so:
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
I'm getting err:
image: unknown format
Any ideas?
So, it looks like this may be an issue with the github.com/disintegration/imaging lib. I posted an issue about it in the repo's page.
If I change my GetBytes() function to this, I get valid JPEG:
func (s SpriteImage) GetBytes() ([]byte, error) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
if s.sprite == nil {
return nil, fmt.Errorf("sprite is nil")
}
im := s.sprite
err := jpeg.Encode(w, im, &jpeg.Options{jpegCompression})
if err != nil {
fmt.Println(err)
}
return b.Bytes(), nil
}
Maybe something to check: The image: unknown format is a common error when you don't include the image/jpeg lib and specifically decode it with it:
import (
"image/jpeg"
"io"
)
...
img, err := jpeg.Decode(r)
if err != nil {
return err
}
...
Some try to use image.Decode, but you still need to include the image/jpeg lib.
I believe it works with a blind include:
import (
_ "image/jpeg"
"image"
"io"
)
...
img, err := image.Decode(r)
if err != nil {
return err
}
...

Go image upload

I am currently uploading an image to my server doing the following:
func (base *GuildController) GuildLogo(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
...
logo, _, err := req.FormFile("logo")
defer logo.Close()
logoGif, format, err := image.Decode(logo)
if err != nil {
base.Error = "Error while decoding your guild logo"
return
}
logoImage, err := os.Create(pigo.Config.String("template")+"/public/guilds/"+ps.ByName("name")+".gif")
if err != nil {
base.Error = "Error while trying to open guild logo image"
return
}
defer logoImage.Close()
//resizedLogo := resize.Resize(64, 64, logoGif, resize.Lanczos3)
err = gif.Encode(logoImage, logoGif, &gif.Options{
256,
nil,
nil,
})
if err != nil {
base.Error = "Error while encoding your guild logo"
return
}
...
}
So everything is working good. But gifs lose the animation.
For example here is a gif I want to upload
And here is the saved one
Not sure what I am doing wrong
As hinted in the comments, you are just working with one frame:
func Decode(r io.Reader) (image.Image, error) Decode reads a GIF image
from r and returns the first embedded image as an image.Image.
But you need
func DecodeAll(r io.Reader) (*GIF, error) DecodeAll reads a GIF image
from r and returns the sequential frames and timing information.
and
func EncodeAll(w io.Writer, g *GIF) error EncodeAll writes the images
in g to w in GIF format with the given loop count and delay between
frames.
Look at this post for details.
Here's an example that slows down the image to 0.5s each frame:
package main
import (
"image/gif"
"os"
)
func main() {
logo, err := os.Open("yay.gif")
if err != nil {
panic(err)
}
defer logo.Close()
inGif, err := gif.DecodeAll(logo)
if err != nil {
panic(err)
}
outGif, err := os.Create("done.gif")
if err != nil {
panic(err)
}
defer outGif.Close()
for i := range inGif.Delay {
inGif.Delay[i] = 50
}
if err := gif.EncodeAll(outGif, inGif); err != nil {
panic(err)
}
}
Results:
Side note
Even if in my browser (Firefox) I see the output image animated, and I can see the the frames in The GIMP, I cannot see it animated on my desktop viewers (gifview, comix). I do not know (yet) what is the cause of this.

Resources