diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b9439e --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +.DS_Store +.vscode/ +vendor/*/ +images +cmd/images +cmd/config.yml +cmd/cmd +cmd/bin +qanswer +config.yml \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..90c5c75 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +build: + cd cmd && go build -o ../qanswer diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c43888 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# 快速答题 + +《冲顶大会》,《百万英雄》... 答题神奇,顺利吃鸡! + +通过抓取手机屏幕截图经过文字识别,结合搜索引擎给出一个参考值。 + +![题目](./docs/screenshot.png) + +分析结果: + +![结果](./docs/answer.png) + +**结果说明:** + +- 结果数:通过题目+答案的搜索形式在搜索引擎中的结果数量 + +- 答案出现频率:通过搜索题目,答案在第一页结果中出现的频率 + +结果并不是100%的,只给出一个参考值,还需用户自己判断。理论上可支持多款APP,只需要修改`config.yml`中的题目和答案的截取位置即可。 + + +## 编译安装 + +安装go环境,执行 `make build` 生成`qanswer`可执行文件。 + +### 配置文件参考 +执行`qanswer`时,默认读取当前目录下的`config.yml`配置文件。 + +``` +# 是否开始调试模式 +debug: false +# 对应的设备类型:ios or android +device: ios +# 使用的ocr工具:baidu or tesseract +ocr_type: baidu +# ios 设备连接wda的地址 +wda_address: '127.0.0.1:8100' +# 截取题目的位置 : +question_x: 30 +question_y: 310 +question_w: 650 +question_h: 135 +# 截取答案的位置 +answer_x: 30 +answer_y: 500 +answer_w: 680 +answer_h: 370 +#当选用baidu ocr时,需要执行api_key和secret_key +baidu_api_key: "xxx...." +baidu_secret_key: "xxx...." + +``` + +### iOS + +- 安装WDA :[iOS 真机如何安装 WebDriverAgent](https://testerhome.com/topics/7220) +- 编译得到`qanswer`文件 +- 根据设备尺寸以及答题APP,修改题目和答案截取位置参数 +- 开始 + + +### Android + +> TIPS:未验证 + + + +## 百度ocr +`ocr_type: baidu` + +如果使用[百度ocr](https://cloud.baidu.com/product/ocr.html),则需要预先申请api key 和secret key ,并且免费的额度有限 + +## tesseract +`ocr_type: tesseract` + +安装tesseract以及简体中文包。 + +以mac:为例 + +``` +brew install tesseract +cd /usr/local/Cellar/tesseract/{version}/share/tessdata +wget https://github.com/tesseract-ocr/tessdata/raw/master/chi_sim.traineddata +``` + +其他系统的安装说明:https://github.com/tesseract-ocr/tesseract/wiki + + + +### TODO: + +- 验证android adb方式 +- 更友好,更快的展示 +- 支持google搜索 +- 不同机型,不同答题app的配置参数 + diff --git a/cmd/gowatch.yml b/cmd/gowatch.yml new file mode 100644 index 0000000..7fdeaa2 --- /dev/null +++ b/cmd/gowatch.yml @@ -0,0 +1,3 @@ +output: ./bin/qanswer +watch_paths: + - ../ diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..448bce8 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/silenceper/qanswer" + +func main() { + qanswer.Run() +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..d83c5c6 --- /dev/null +++ b/config/config.go @@ -0,0 +1,54 @@ +package config + +import ( + "io/ioutil" + "path/filepath" + + yaml "gopkg.in/yaml.v1" +) + +//Config 全局配置 +type Config struct { + Debug bool `yaml:"debug"` + Device string `yaml:"device"` + OcrType string `yaml:"ocr_type"` + WdaAddress string `yaml:"wda_address"` + + //baidu ocr + BaiduAPIKey string `yaml:"baidu_api_key"` + BaiduSecretKey string `yaml:"baidu_secret_key"` + + //截图题目位置 + QuestionX int `yaml:"question_x"` + QuestionY int `yaml:"question_y"` + QuestionW int `yaml:"question_w"` + QuestionH int `yaml:"question_h"` + + //截取答案位置 + AnswerX int `yaml:"answer_x"` + AnswerY int `yaml:"answer_y"` + AnswerW int `yaml:"answer_w"` + AnswerH int `yaml:"answer_h"` +} + +var cfg *Config + +//GetConfig 解析配置 +func GetConfig() *Config { + if cfg != nil { + return cfg + } + filename, _ := filepath.Abs("./config.yml") + yamlFile, err := ioutil.ReadFile(filename) + + if err != nil { + panic(err) + } + var c *Config + err = yaml.Unmarshal(yamlFile, &c) + if err != nil { + panic(err) + } + cfg = c + return cfg +} diff --git a/docs/answer.png b/docs/answer.png new file mode 100644 index 0000000..bbf0a22 Binary files /dev/null and b/docs/answer.png differ diff --git a/docs/screenshot.png b/docs/screenshot.png new file mode 100644 index 0000000..f1a0a57 Binary files /dev/null and b/docs/screenshot.png differ diff --git a/gowatch.yml b/gowatch.yml new file mode 100644 index 0000000..7fdeaa2 --- /dev/null +++ b/gowatch.yml @@ -0,0 +1,3 @@ +output: ./bin/qanswer +watch_paths: + - ../ diff --git a/image.go b/image.go new file mode 100644 index 0000000..23fb0d2 --- /dev/null +++ b/image.go @@ -0,0 +1,53 @@ +package qanswer + +import ( + "fmt" + "image" + + "github.com/ngaut/log" + "github.com/silenceper/qanswer/config" + "github.com/silenceper/qanswer/proto" + "github.com/silenceper/qanswer/util" +) + +func saveImage(png image.Image, cfg *config.Config) error { + screenshotPath := fmt.Sprintf("%s/screenshot.png", proto.ImagePath) + err := util.SavePNG(screenshotPath, png) + if err != nil { + return fmt.Errorf("保存截图失败,%v", err) + } + log.Debugf("保存完整截图成功,%s", screenshotPath) + + //裁剪图片 + questionImg, answerImg, err := cutImage(png, cfg) + if err != nil { + return fmt.Errorf("截图失败,%v", err) + } + err = util.SavePNG(proto.QuestionImage, questionImg) + if err != nil { + return fmt.Errorf("保存question截图失败,%v", err) + } + log.Debugf("保存question截图成功") + err = util.SavePNG(proto.ImagePath+"/answer.png", answerImg) + if err != nil { + return fmt.Errorf("保存answer截图失败,%v", err) + + } + log.Debugf("保存answer截图成功") + return nil +} + +func cutImage(src image.Image, cfg *config.Config) (questionImg image.Image, answerImg image.Image, err error) { + questionImg, err = util.CutImage(src, cfg.QuestionX, cfg.QuestionY, cfg.QuestionW, cfg.QuestionH) + //questionImg, err = util.CutImage(src, 30, 310, 650, 135) + if err != nil { + return + } + + answerImg, err = util.CutImage(src, cfg.AnswerX, cfg.AnswerY, cfg.AnswerW, cfg.AnswerH) + // answerImg, err = util.CutImage(src, 30, 500, 680, 370) + if err != nil { + return + } + return +} diff --git a/ocr.go b/ocr.go new file mode 100644 index 0000000..5d0dc39 --- /dev/null +++ b/ocr.go @@ -0,0 +1,20 @@ +package qanswer + +import ( + "github.com/silenceper/qanswer/config" + "github.com/silenceper/qanswer/ocr" + "github.com/silenceper/qanswer/proto" +) + +//Ocr ocr 识别图片文字 +type Ocr interface { + GetText(imgPath string) (string, error) +} + +//NewOcr 使用哪种ocr识别 +func NewOcr(cfg *config.Config) Ocr { + if cfg.OcrType == proto.OcrTesseract { + return ocr.NewTesseract(cfg) + } + return ocr.NewBaidu(cfg) +} diff --git a/ocr/baidu.go b/ocr/baidu.go new file mode 100644 index 0000000..6a46a47 --- /dev/null +++ b/ocr/baidu.go @@ -0,0 +1,96 @@ +package ocr + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + "sync" + + "github.com/silenceper/qanswer/config" + "github.com/silenceper/qanswer/util" +) + +//Baidu baidu ocr api +type Baidu struct { + apiKey string + secretKey string + + accessToken string + sync.RWMutex +} + +type accessTokenRes struct { + AccessToken string `json:"access_token"` + ExpiresIn int32 `json:"expires_in"` +} + +//wordsResults 匹配 +type wordsResults struct { + WordsNum int32 `json:"words_result_num"` + WordsResult []struct { + Words string `json:"words"` + } `json:"words_result"` +} + +//NewBaidu new +func NewBaidu(cfg *config.Config) *Baidu { + baidu := new(Baidu) + baidu.apiKey = cfg.BaiduAPIKey + baidu.secretKey = cfg.BaiduSecretKey + return baidu +} + +//GetText 识别图片中的文字 +func (baidu *Baidu) GetText(imgPath string) (string, error) { + accessToken, err := baidu.getAccessToken() + if err != nil { + return "", err + } + base64Data, err := util.OpenImageToBase64(imgPath) + if err != nil { + return "", err + } + uri := fmt.Sprintf("https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token=%s", accessToken) + + postData := url.Values{} + postData.Add("image", base64Data) + body, err := util.PostForm(uri, postData) + if err != nil { + return "", err + } + wordResults := new(wordsResults) + err = json.Unmarshal(body, wordResults) + if err != nil { + return "", err + } + var text string + for _, words := range wordResults.WordsResult { + text = fmt.Sprintf("%s\n%s", text, strings.TrimSpace(words.Words)) + } + text = strings.TrimLeft(text, "\n") + return text, nil +} + +func (baidu *Baidu) getAccessToken() (accessToken string, err error) { + baidu.Lock() + defer baidu.Unlock() + if baidu.accessToken != "" { + accessToken = baidu.accessToken + return + } + uri := fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s", baidu.apiKey, baidu.secretKey) + body, e := util.PostJSON(uri, nil, "application/json;charset=utf-8") + if e != nil { + err = e + return + } + res := new(accessTokenRes) + err = json.Unmarshal(body, res) + if err != nil { + return + } + accessToken = res.AccessToken + baidu.accessToken = accessToken + return +} diff --git a/ocr/tesseract.go b/ocr/tesseract.go new file mode 100644 index 0000000..b39adfd --- /dev/null +++ b/ocr/tesseract.go @@ -0,0 +1,25 @@ +package ocr + +import ( + "github.com/otiai10/gosseract" + "github.com/silenceper/qanswer/config" +) + +//Tesseract tesseract 识别 +type Tesseract struct{} + +//NewTesseract new +func NewTesseract(cfg *config.Config) *Tesseract { + return new(Tesseract) +} + +//GetText 根据图片路径获取识别文字 +func (tesseract *Tesseract) GetText(imgPath string) (string, error) { + client := gosseract.NewClient() + defer client.Close() + + client.SetImage(imgPath) + client.SetLanguage("chi_sim") + + return client.Text() +} diff --git a/proto/const.go b/proto/const.go new file mode 100644 index 0000000..c10674e --- /dev/null +++ b/proto/const.go @@ -0,0 +1,13 @@ +package proto + +const ( + DeviceiOS = "ios" + DeviceAndroid = "android" + + OcrTesseract = "tesseract" + OcrBaidu = "baidu" + + ImagePath = "./images/" + QuestionImage = ImagePath + "question.png" + AnswerImage = ImagePath + "answer.png" +) diff --git a/qanswer.go b/qanswer.go new file mode 100644 index 0000000..3b32c0e --- /dev/null +++ b/qanswer.go @@ -0,0 +1,131 @@ +package qanswer + +import ( + "regexp" + "strings" + "sync" + + "github.com/fatih/color" + "github.com/ngaut/log" + termbox "github.com/nsf/termbox-go" + "github.com/silenceper/qanswer/config" + "github.com/silenceper/qanswer/proto" + "github.com/silenceper/qanswer/util" +) + +//Run start run +func Run() { + + cfg := config.GetConfig() + err := util.MkDirIfNotExist(proto.ImagePath) + if err != nil { + panic(err) + } + err = termbox.Init() + if err != nil { + panic(err) + } + defer termbox.Close() + + if !cfg.Debug { + log.SetLevel(log.LOG_LEVEL_INFO) + } + + color.Cyan("基本配置:") + color.Cyan("平台:%s; 图片识别方式:%s", cfg.Device, cfg.OcrType) + color.Yellow("\n请按空格键开始搜索答案:") + +Loop: + for { + switch ev := termbox.PollEvent(); ev.Type { + case termbox.EventKey: + switch ev.Key { + case termbox.KeySpace: + answerQuestion(cfg) + color.Yellow("\n\n请按空格键开始搜索答案:") + default: + break Loop + } + } + } + +} + +func answerQuestion(cfg *config.Config) { + color.Cyan("正在开始搜索....") + //区分ios 或android 获取图像 + screenshot := NewScreenshot(cfg) + png, err := screenshot.GetImage() + if err != nil { + log.Errorf("获取截图失败,%v", err) + return + } + err = saveImage(png, cfg) + if err != nil { + log.Errorf("保存图片失败,%v", err) + return + } + + //识别文字 + ocr := NewOcr(cfg) + var wg sync.WaitGroup + wg.Add(2) + + var questionText string + go func() { + defer wg.Done() + questionText, err = ocr.GetText(proto.QuestionImage) + if err != nil { + log.Errorf("识别题目失败,%v", err) + return + } + questionText = processQuestion(questionText) + }() + + var answerArr []string + go func() { + defer wg.Done() + answerText, err := ocr.GetText(proto.AnswerImage) + if err != nil { + log.Errorf("识别答案失败,%v", err) + return + } + answerArr = processAnswer(answerText) + }() + wg.Wait() + + if cfg.Debug { + color.Yellow("识别题目:") + color.Green("%s", questionText) + color.Yellow("识别答案:") + color.Green("%v", answerArr) + } + + //搜索答案并显示 + result := GetSearchResult(questionText, answerArr) + for engine, answerResult := range result { + color.Yellow("\n%s的搜索结果:", engine) + color.Cyan("题目:%s \n", questionText) + for key, val := range answerResult { + color.Green("%s : 结果数 %d , 答案出现频率: %d", answerArr[key], val.Sum, val.Freq) + } + } +} + +func processQuestion(text string) string { + log.Debug(text) + text = strings.Replace(text, "\n", "", -1) + text = strings.Replace(text, "\r", "", -1) + + //去除编号 + re, _ := regexp.Compile("\\d\\.") + text = re.ReplaceAllString(text, "") + return text +} + +func processAnswer(text string) []string { + log.Debug(text) + text = strings.Replace(text, " ", "", -1) + arr := strings.Split(text, "\n") + return arr +} diff --git a/screenshot.go b/screenshot.go new file mode 100644 index 0000000..29ef569 --- /dev/null +++ b/screenshot.go @@ -0,0 +1,22 @@ +package qanswer + +import ( + "image" + + "github.com/silenceper/qanswer/config" + "github.com/silenceper/qanswer/proto" + "github.com/silenceper/qanswer/screenshot" +) + +//Screenshot 获取屏幕截图 +type Screenshot interface { + GetImage() (image.Image, error) +} + +//NewScreenshot new +func NewScreenshot(cfg *config.Config) Screenshot { + if cfg.Device == proto.DeviceiOS { + return screenshot.NewIOS(cfg) + } + return screenshot.NewAndroid(cfg) +} diff --git a/screenshot/android.go b/screenshot/android.go new file mode 100644 index 0000000..ccceb65 --- /dev/null +++ b/screenshot/android.go @@ -0,0 +1,33 @@ +package screenshot + +import ( + "image" + "os/exec" + + "github.com/silenceper/qanswer/config" + "github.com/silenceper/qanswer/proto" + "github.com/silenceper/qanswer/util" +) + +//Android android +type Android struct{} + +//NewAndroid new +func NewAndroid(cfg *config.Config) *Android { + return new(Android) +} + +//GetImage 通过adb获取截图 +func (android *Android) GetImage() (img image.Image, err error) { + err = exec.Command("adb", "shell", "screencap", "-p", "/sdcard/screenshot.png").Run() + if err != nil { + return + } + originImagePath := proto.ImagePath + "origin.png" + err = exec.Command("adb", "pull", "/sdcard/screenshot.png", originImagePath).Run() + if err != nil { + return + } + img, err = util.OpenPNG(originImagePath) + return +} diff --git a/screenshot/ios.go b/screenshot/ios.go new file mode 100644 index 0000000..854531d --- /dev/null +++ b/screenshot/ios.go @@ -0,0 +1,63 @@ +package screenshot + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "image" + "image/png" + + "github.com/silenceper/qanswer/config" + "github.com/silenceper/qanswer/util" +) + +//IOS 获取iOS截图 +type IOS struct { + wdaAddress string +} + +type screenshotRes struct { + Value string `json:"value"` + SessionID string `json:"sessionId"` + Status int `json:"status"` +} + +//NewIOS new +func NewIOS(cfg *config.Config) *IOS { + ios := new(IOS) + ios.wdaAddress = cfg.WdaAddress + if ios.wdaAddress == "" { + panic("请指定 wda 连接地址") + } + return ios +} + +//GetImage 返回图片生成的路径 +func (ios *IOS) GetImage() (img image.Image, err error) { + body, e := util.HTTPGet(fmt.Sprintf("http://%s/screenshot", ios.wdaAddress)) + if e != nil { + err = fmt.Errorf("WebDriverAgentRunner 连接失败, err=%v", e) + return + } + + res := new(screenshotRes) + e = json.Unmarshal(body, res) + if err != nil { + err = fmt.Errorf("WebDriverAgentRunner 响应数据异常,请检查 WebDriverAgentRunner 运行状态, err=%v", e) + return + } + pngValue, e := base64.StdEncoding.DecodeString(res.Value) + if err != nil { + err = fmt.Errorf("图片解码失败, err=%v", e) + return + } + + src, err := png.Decode(bytes.NewReader(pngValue)) + if err != nil { + err = fmt.Errorf("图片解码失败, err=%v", e) + return + } + img = src + return +} diff --git a/search.go b/search.go new file mode 100644 index 0000000..8ef4bf4 --- /dev/null +++ b/search.go @@ -0,0 +1,86 @@ +package qanswer + +import ( + "fmt" + "net/url" + "regexp" + "strings" + "sync" + + "github.com/ngaut/log" + "github.com/silenceper/qanswer/util" +) + +//SearchResult 搜索总数跟频率 +type SearchResult struct { + Sum int32 + Freq int32 +} + +//GetSearchResult 返回各个搜索引擎返回的结果 +func GetSearchResult(question string, answers []string) map[string][]*SearchResult { + if question == "" { + return nil + } + res := make(map[string][]*SearchResult) + res["百度"] = baiduSearch(question, answers) + return res +} + +func baiduSearch(question string, answers []string) (result []*SearchResult) { + resultMap := make(map[string]*SearchResult, len(answers)) + + //搜索题目 + searchURL := fmt.Sprintf("http://www.baidu.com/s?wd=%s", url.QueryEscape(question)) + questionBody, err := util.HTTPGet(searchURL) + if err != nil { + log.Errorf("search question:%s error", question) + return + } + + var wg sync.WaitGroup + + for k, answer := range answers { + answer = plainAnswer(answer) + answers[k] = answer + + wg.Add(1) + go func(answer string) { + defer wg.Done() + searchResult := new(SearchResult) + //题目搜索结果中包含的答案的数量 + searchResult.Freq = int32(strings.Count(string(questionBody), answer)) + + //题目+结果搜索的总数 + keyword := fmt.Sprintf("%s %s", question, answer) + searchURL := fmt.Sprintf("http://www.baidu.com/s?wd=%s", url.QueryEscape(keyword)) + body, err := util.HTTPGet(searchURL) + if err != nil { + log.Errorf("search %s error", answer) + } else { + countRe, _ := regexp.Compile(`百度为您找到相关结果约([\d\,]+)`) + result := countRe.FindAllStringSubmatch(string(body), -1) + if len(result) > 0 { + sum := result[0][1] + sum = strings.Replace(sum, ",", "", -1) + searchResult.Sum = util.MustInt32(sum) + } + } + resultMap[answer] = searchResult + }(answer) + } + wg.Wait() + + //将map转为slice 方便顺序输出 + for _, answer := range answers { + result = append(result, resultMap[answer]) + } + return result +} + +//plainAnswer 去除答案中的 《》等字符 +func plainAnswer(answer string) string { + answer = strings.TrimPrefix(answer, "《") + answer = strings.TrimSuffix(answer, "》") + return answer +} diff --git a/util/http.go b/util/http.go new file mode 100644 index 0000000..6893bc4 --- /dev/null +++ b/util/http.go @@ -0,0 +1,62 @@ +package util + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" +) + +//HTTPGet get 请求 +func HTTPGet(uri string) ([]byte, error) { + response, err := http.Get(uri) + if err != nil { + return nil, err + } + + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode) + } + return ioutil.ReadAll(response.Body) +} + +//PostJSON post json 数据请求 +func PostJSON(uri string, obj interface{}, contentType string) ([]byte, error) { + jsonData, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1) + jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1) + jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1) + + body := bytes.NewBuffer(jsonData) + response, err := http.Post(uri, contentType, body) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("http post error : uri=%v , statusCode=%v", uri, response.StatusCode) + } + return ioutil.ReadAll(response.Body) +} + +//PostForm 提交表单 +func PostForm(uri string, data url.Values) ([]byte, error) { + response, err := http.PostForm(uri, data) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("http post error : uri=%v , statusCode=%v", uri, response.StatusCode) + } + return ioutil.ReadAll(response.Body) +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..7fcb188 --- /dev/null +++ b/util/util.go @@ -0,0 +1,74 @@ +package util + +import ( + "encoding/base64" + "errors" + "image" + "image/png" + "io/ioutil" + "os" + "strconv" +) + +// MkDirIfNotExist 如果指定文件夹不存在则创建 +func MkDirIfNotExist(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return os.MkdirAll(path, 0755) + } + return nil +} + +//OpenImageToBase64 OpenImageToBase64 +func OpenImageToBase64(filename string) (string, error) { + f, err := ioutil.ReadFile(filename) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(f), nil +} + +//OpenPNG 打开png图片 +func OpenPNG(filename string) (image.Image, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + return png.Decode(f) +} + +//SavePNG 保存png图片 +func SavePNG(filename string, pic image.Image) error { + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return err + } + defer f.Close() + return png.Encode(f, pic) +} + +//CutImage 裁剪图片 +func CutImage(src image.Image, x, y, w, h int) (image.Image, error) { + var subImg image.Image + + if rgbImg, ok := src.(*image.YCbCr); ok { + subImg = rgbImg.SubImage(image.Rect(x, y, x+w, y+h)).(*image.YCbCr) //图片裁剪x0 y0 x1 y1 + } else if rgbImg, ok := src.(*image.RGBA); ok { + subImg = rgbImg.SubImage(image.Rect(x, y, x+w, y+h)).(*image.RGBA) //图片裁剪x0 y0 x1 y1 + } else if rgbImg, ok := src.(*image.NRGBA); ok { + subImg = rgbImg.SubImage(image.Rect(x, y, x+w, y+h)).(*image.NRGBA) //图片裁剪x0 y0 x1 y1 + } else { + return subImg, errors.New("图片解码失败") + } + + return subImg, nil +} + +//MustInt32 MustInt32 +func MustInt32(str string) int32 { + i, err := strconv.ParseInt(str, 10, 32) + if err != nil { + return int32(0) + } + return int32(i) +} diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 0000000..1fcb208 --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,64 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "checksumSHA1": "p5z5hdUt68Z3tK7Is+yLGrCNzoA=", + "path": "github.com/fatih/color", + "revision": "5df930a27be2502f99b292b7cc09ebad4d0891f4", + "revisionTime": "2017-09-26T11:14:11Z" + }, + { + "checksumSHA1": "MrqRXuqj5suGXqcRUUwi+YA7PmE=", + "origin": "github.com/fatih/color/vendor/github.com/mattn/go-colorable", + "path": "github.com/mattn/go-colorable", + "revision": "5df930a27be2502f99b292b7cc09ebad4d0891f4", + "revisionTime": "2017-09-26T11:14:11Z" + }, + { + "checksumSHA1": "a/chskqRYBBHS8lDTpB3FQqgHb8=", + "origin": "github.com/fatih/color/vendor/github.com/mattn/go-isatty", + "path": "github.com/mattn/go-isatty", + "revision": "5df930a27be2502f99b292b7cc09ebad4d0891f4", + "revisionTime": "2017-09-26T11:14:11Z" + }, + { + "checksumSHA1": "cJE7dphDlam/i7PhnsyosNWtbd4=", + "path": "github.com/mattn/go-runewidth", + "revision": "97311d9f7767e3d6f422ea06661bc2c7a19e8a5d", + "revisionTime": "2017-05-10T07:48:58Z" + }, + { + "checksumSHA1": "94Mvr/SU9I9Zl3pBtbHsBPN0LTg=", + "path": "github.com/ngaut/log", + "revision": "d2af3a61f64d093457fb23b25d20f4ce3cd551ce", + "revisionTime": "2017-03-07T01:10:05Z" + }, + { + "checksumSHA1": "5Jd0+a1X5Nvhhw8VfSUD9i1WyP0=", + "path": "github.com/nsf/termbox-go", + "revision": "aa4a75b1c20a2b03751b1a9f7e41d58bd6f71c43", + "revisionTime": "2017-11-04T16:23:16Z" + }, + { + "checksumSHA1": "gOBaM54JHx9XEjZAl81GGOJr0Ic=", + "path": "github.com/otiai10/gosseract", + "revision": "63ad056c2e74a796684a8e9312a839aa5c76c8f3", + "revisionTime": "2017-12-03T14:31:49Z" + }, + { + "checksumSHA1": "oBjdFvDUZpioGsB6URqe16hDQUg=", + "origin": "github.com/fatih/color/vendor/golang.org/x/sys/unix", + "path": "golang.org/x/sys/unix", + "revision": "5df930a27be2502f99b292b7cc09ebad4d0891f4", + "revisionTime": "2017-09-26T11:14:11Z" + }, + { + "checksumSHA1": "MGk7cSnHqiL5soaovzgcJaNH3fc=", + "path": "gopkg.in/yaml.v1", + "revision": "9f9df34309c04878acc86042b16630b0f696e1de", + "revisionTime": "2014-09-24T16:16:07Z" + } + ], + "rootPath": "github.com/silenceper/qanswer" +}