改变照片位置信息

此分析是在iphone7拍摄照片基础上做出的,是否是通用情况还需测试。

jpeg格式文件以'0xFFD8'开始,以'0xFFD9'(大端存储)

获取照片元数据信息:
查找Exif区域:FF E1 16 9F 45 78 69 66 00 00 4D 4D 00 2A 00 00 00 08 00 0B
FF E1 Exif段开始标志, 后面两字节是Exif段大小, 再后面45 78 69 66 00 00是Exif的ascii码和两个零字节。
4D 4D表示后面的数据区用大端字节序存储,对应还有小端用49 49表示。
00 2A00 00 00 08 是固定的。分别是0x002A,因为是大端所以如此存储。
00 0B是后面数据区大小(数据区包括这两个字节本身)。

寻找修改时间:0x0132
子数据区:0x8769,里面有两个时间:0x90030x9004

GPS信息区域:0x8825,里面有如下:

0x0001:‘N’或’S’
0x0002:纬度,8字节3
0x0003:‘E’或’W’
0x0004:经度,8字节
3
0x0005:0海平面上,1海平面下
0x0006:海拔,8字节
0x0007:UTC时间的时分秒,8字节*3。一般是我们的时间减8小时,分秒不变。
0x001d:11个字节的字符(包括结尾的0字节),时间戳

以下是程序示例(Scala语言)

github:https://github.com/xuejianbest/ChangeImageGPSInfo

在解析高德API返回的坐标信息时候,依赖了 json4s库:

val json4sNative = "org.json4s" %% "json4s-native" % "3.2.11"

程序:

import scala.io.Source
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
import org.json4s._
import java.io.File
import java.io.FileInputStream
import scala.collection.mutable.ArrayBuffer
import java.io.FileOutputStream

object Jpggps { 
    def main(args: Array[String]): Unit = { 
        val city = "北京"
        val addr = "天安门"
        val file = "d:/test.jpg"
        setChinaGPS(city, addr, file)
        println("位置修改完成")
    }

    /* * 根据地理位置,修改照片坐标坐标 */
    def setChinaGPS(city: String, addr: String, jpg: String) { 
        val jp = new Jpggps(jpg)
        val map = jp.getGPSidx
        val lngidx = map.getOrElse("Lng_24", 0)
        val latidx = map.getOrElse("Lat_24", 0)

        if(lngidx * latidx == 0){ 
            println("获取不到原始文件的经纬度信息!")
            return
        }
        val (lng1, lng2, lng3, lat1, lat2, lat3) = getGPS(city, addr)
        val a1 = jp.int2Bytes(1)
        val a100 = jp.int2Bytes(100)
        val arr_lng = jp.int2Bytes(lng1)
            .union(a1)
            .union(jp.int2Bytes(lng2))
            .union(a1)
            .union(jp.int2Bytes(lng3))
            .union(a100)
        val arr_lat = jp.int2Bytes(lat1)
            .union(a1)
            .union(jp.int2Bytes(lat2))
            .union(a1)
            .union(jp.int2Bytes(lat3))
            .union(a100)
        val bs = jp.bytes
            .patch(lngidx, arr_lng, arr_lng.size)
            .patch(latidx, arr_lat, arr_lat.size)
        val file = new File(jpg+".gps.jpg")
        val out = new FileOutputStream(file)
        out.write(bs)
        out.close()
    }

    /* * 根据地理位置,获取高德经纬度坐标 */
    def getGPS(city: String, addr: String) = { 
        val myKey = "**********" //高德API Key
        val url = s"""https://restapi.amap.com/v3/geocode/geo?key=${myKey}&address=${addr}&city=${city}"""
        val resp = Source.fromURL(url, "utf-8").mkString

        val jValue = parse(resp)
        // println(pretty(render(jValue)))

        val loc = (jValue \ "geocodes").asInstanceOf[JArray].apply(0) \ "location"
        val str = loc.asInstanceOf[JString].s

        val lng = str.split(",")(0).toDouble
        val lat = str.split(",")(1).toDouble
        //println(lng)
        //println(lat)

        val lng1 = lng.toInt
        val lng2 = ((lng - lng1) * 60).toInt
        val lng3 = ((((lng - lng1) * 60 - lng2) * 60) * 100).toInt

        val lat1 = lat.toInt
        val lat2 = ((lat - lat1) * 60).toInt
        val lat3 = ((((lat - lat1) * 60 - lat2) * 60) * 100).toInt

        (lng1, lng2, lng3, lat1, lat2, lat3)
    }

}

/* * jpg照片类,一个实例代表一张照片 * 里面有获取照片信息的相关方法 */
class Jpggps private { 
    var filename: String = null
    var bytes: Array[Byte] = null
    var index: Int = 0
    var bigEndian: Boolean = false //jpgExif元信息是否为大端字节序

    def this(filename: String) = { 
        this()
        this.filename = filename
        read_bytes
        find_index
        big_or_little
    }

    /* * 将照片内容读取为字节数组 */
    private def read_bytes() = { 
        val file = new File(filename)
        val in = new FileInputStream(new File(filename))
        bytes = new Array[Byte](file.length.toInt)
        in.read(bytes)
        in.close()
    }

    /* * 获取Exif元信息偏移量位置 */
    private def find_index() = { 
        val arr1 = Array(0xff.toByte, 0xe1.toByte)
        val arr2 = Array(0x45.toByte, 0x78.toByte, 0x69.toByte, 0x66.toByte, 0x00.toByte, 0x00.toByte)

        val buff = new ArrayBuffer[Int]()
        var idxt = bytes.indexOfSlice(arr1)
        while (idxt != -1) { 
            buff.append(idxt)
            idxt = bytes.indexOfSlice(arr1, idxt + 1)
        }
        val buff2 = new ArrayBuffer[Int]()
        var idxt2 = bytes.indexOfSlice(arr2)
        while (idxt2 != -1) { 
            buff2.append(idxt2)
            idxt2 = bytes.indexOfSlice(arr2, idxt2 + 1)
        }

        val indexs = buff.filter(idx => buff2.contains(idx + 4)).toArray
        require(indexs.size == 1, s"找到Exif头部${indexs.size}次!")
        index = indexs(0) + 10
    }

    override def toString() = { 
        s"jpggps(path:${filename}, index:${index}, bigEndian:${bigEndian})"
    }

    /* * 判断Exif元信息字节序 */
    private def big_or_little() { 
        if (bytes(index) == 0x4d.toByte) { 
            bigEndian = true
        } else if (bytes(index) == 0x49.toByte) { 
            bigEndian = false
        } else { 
            throw new IllegalArgumentException("字节序标识字符串解析错误!")
        }
    }

    /* * 获取照片GPS信息 */
    def getGPSidx() = { 
        val b8 = bytes.slice(index + 8, index + 10)
        val num = bytes2Int(b8)

        val arr = (0 until num).filter { 
            i =>
                val idx = index + 10 + 12 * i
                bytes2Int(bytes.slice(idx, idx + 2)) == 0x8825
        }
        var map = scala.collection.mutable.Map[String, Int]()
        if (arr.size == 0) { 
            println("没有GPS信息")
        } else { 
            val idx = index + 10 + 12 * arr(0)
            val gps_index = bytes2Int(bytes.slice(idx + 10, idx + 12)) + index
            val count = bytes2Int(bytes.slice(gps_index, gps_index + 2))

            (0 until count).foreach { 
                i =>
                    val idx = gps_index + 2 + 12 * i
                    val arr = bytes.slice(idx, idx + 2)
                    bytes2Int(arr) match { 
                        case 0x0001 => map.put("N_S_2", idx + 8)
                        case 0x0002 => map.put("Lat_24", bytes2Int(bytes.slice(idx + 8, idx + 12)) + index)
                        case 0x0003 => map.put("E_W_2", idx + 8)
                        case 0x0004 => map.put("Lng_24", bytes2Int(bytes.slice(idx + 8, idx + 12)) + index)
                        case 0x0005 => map.put("0Up_1Down_1", idx + 8)
                        case 0x0006 => map.put("High_8", bytes2Int(bytes.slice(idx + 8, idx + 12)) + index)
                        case 0x0007 => map.put("UTC_24", bytes2Int(bytes.slice(idx + 8, idx + 12)) + index)
                        case 0x001d => map.put("Date_11", bytes2Int(bytes.slice(idx + 8, idx + 12)) + index)
                        case _ => None
                    }
            }
        }
        map
    }

    def bytes2Int(arr: Array[Byte]) = { 
        require(arr != null && arr.size <= 4, "bytes2Int: 参数字不能为null且字节数量只能为0-4")

        var res = 0
        var arrt = arr
        if (bigEndian) { 
            arrt = arr.reverse
        }

        var sca = 1
        arrt.foreach { 
            b =>
                res += (b & 0xff) * sca
                sca *= 256
        }
        res
    }

    def int2Bytes(i: Int) = { 
        var arr = new Array[Byte](4)
        arr(0) = (i & 0xff).toByte
        arr(1) = ((i >>> 8) & 0xff).toByte
        arr(2) = ((i >>> 16) & 0xff).toByte
        arr(3) = ((i >>> 24) & 0xff).toByte

        var res = arr
        if (bigEndian) { 
            res = arr.reverse
        }
        res
    }
}
    原文作者:xuejianbest
    原文地址: https://blog.csdn.net/xuejianbest/article/details/80405478
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞