java實現,使用opencv合成全景圖,前端使用krpano展示


這周花三天做了一demo,算上之前的,怎么也有五天,上一篇是opencv介紹,以及定義native方法,通過本地圖片路徑傳參,底層調用Opencv圖像庫合成,有興趣的可以看看,這篇重點在於krpano的全景圖展示,話說剛才上傳了22張片照片合成全景圖,感覺有半個小時也沒有合成完,我這電腦是有多垃圾

那我們代碼走一走(都說不上代碼是老流氓)

這是demo的目錄結構

@Controller
public class PanoramaController {

     /**
         *    @Description:  文件上傳以及全景合成
         *    @Date:  15:58  2018/7/6
         *    @Params:   * @param null
         */
     
    @RequestMapping("/upload")
    public ModelAndView login07(@RequestParam(value = "file", required = false)MultipartFile[] files,
                          @RequestParam(value = "title",defaultValue = "未命名") String title,HttpServletRequest request) {
        ModelAndView mv=new ModelAndView("success.html");
        MultipartFile tempMultipartFile;
        //基本路徑
        StringBuffer sb=new StringBuffer();
        String baseURL="E:/demo";
        String tempStr = "/" + UUID.randomUUID().toString();
        for (int j = 0; j < files.length; j++) {
            tempMultipartFile = files[j];
            // 獲得上傳的文件名稱-帶有后綴
            String fileNameAndSuffixName = tempMultipartFile.getOriginalFilename();
            // 獲取上傳的文件名稱
            //String fileName = fileNameAndSuffixName.substring(0, fileNameAndSuffixName.lastIndexOf("."));
            String urlPath;
            urlPath = Imgeupload.fileUpdata(tempMultipartFile, "E:/demo", "" + tempStr);
            System.out.println("=====" + urlPath);
            if (j==files.length-2){
                sb.append(baseURL+tempStr+"/"+urlPath);
                break;
            }else if (j<files.length-2){
                sb.append(baseURL+tempStr+"/"+urlPath+",");
            }else {
                System.out.println();
            }

        }
        //拼接URL
        System.out.println("拼接URL"+sb.toString());
        //調用native
        String result= OpenCVUtil.changeArrValue(sb.toString());

        if(!result.contains(",")){
            mv.setViewName("failure.html");
            return mv;
        }
        //System.out.println(result);
        //復制圖片
        String basedirNew=UUID.randomUUID().toString()+","+title;
        String dirNew="D:\\tupian\\img\\"+basedirNew+"\\";
        try {
            copyFile(new File("D:/result.jpg"),new File(dirNew+title+".jpg"),dirNew);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mv.addObject("imgUrl","/img/"+basedirNew+"/"+title+".jpg");
        mv.addObject("title",title);
        mv.addObject("fileName",basedirNew);
        return mv;
    }

    @RequestMapping("/index")
    public String login06() {
        return "upload.html";
    }

     /**
         *    @Description:  復制生成的圖片到全景圖靜態區
         *    @Date:  9:45  2018/7/5
         *    @Params:   * @param null
         */

    public void copyFile(File fromFile, File toFile,String fromUrl) throws IOException {
        File file=new File(fromUrl);
        if (!file.exists()) {
            file.mkdir();
        }
        FileInputStream ins = new FileInputStream(fromFile);
        FileOutputStream out = new FileOutputStream(toFile);
        byte[] b = new byte[1024];
        int n=0;
        while((n=ins.read(b))!=-1){
            out.write(b, 0, n);
        }

        ins.close();
        out.close();
    }

}

  這個主要是文件上傳以及全景圖合成調用native,生成result.jpg全景圖,利用生成的全景圖通過krpano工具,生成相應的文件放在tomcat對應webapp,啟動tomcat即可訪問,這是最開始使用krpano看效果的流程,當然java項目不可能手動復制粘貼文件吧,一切都是通過程序控制,所以不可避免java IO操作,這時遇到一個很大的坑,聽我細細道來

  當時想已經通過krpano生成的相應的文件復制到webapp這部分的操作通過io進行操作,后來才發現,用IO發現權限不夠,我使用的是spring boot 項目,tomcat是嵌入式,所以復制到當前項目classes文件下,那換一條路既然生成的都是靜態文件,那從當前的項目,引用絕對路徑,從本地獲取靜態資源,呵呵,通過指定端口當用的項目,靜態資源權限只限當前項目,從網上收集許多資料,最終使用 

 

spring:
  resources:
    static-locations: //相當於,這塊空間和static目錄下,resource下,webjar下同級,
//而這塊空間可以io進行操作

好了,這個問題解決了,那java代碼如何將某個圖片拖到本地某個應用XX.bat上看如下代碼

public class CmdBat {
	
	/*public static void main(String[] args) {
		Room r = new Room();
		//項目的位置
		String dpath = "D:\\tupian\\vshow";
		//全景圖的位置
		String file = "3";
		String[] fn1 = { "2",
				"3" };
		String[] fn2 = { "客廳", "卧室","大客廳" };
		String title = "哈哈哈哈哈哈哈哈";
		String music = "vshow/backgroundmusic/default.mp3";
		try {
			setKrpano(r,dpath, file, fn1, fn2, title,music);
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("上傳失敗");
		}

	}*/
	     /**
	         *    @Description:
	         *    @Date:  10:15  2018/7/6
	         *    @Params:   * @param null
	         */
	     
	public static void setKrpano(final Room r, final String dpath, final String file,
								 final String[] fn1, final String[] fn2, final String title, final String music)
			throws InterruptedException {
		//全景圖存的位置
		final String temppath = "D:\\tupian\\img\\";
		String path = temppath+file;
		String ex = "krpanotools32.exe makepano -config=templates\\vtour-multires.config "
				+ path + "\\*.jpg";
		//執行
		Runtime runtime = Runtime.getRuntime();
		boolean b = true;
		Process p = null;
		try {
			//krpano  安裝位置
			p = runtime.exec("cmd /c start D:\\Krpano\\krpano.1.19.pr16\\krpano-1.19-pr16\\" + ex);
		} catch (Exception e) {
			b = false;
		}
		if (b) {
			final InputStream is1 = p.getInputStream();
			final InputStream is2 = p.getErrorStream();
			new Thread() {
				public void run() {
					BufferedReader br1 = new BufferedReader(
							new InputStreamReader(is1));
					try {
						String line1 = null;
						while ((line1 = br1.readLine()) != null) {
							if (line1 != null) {
								System.out.println("=AA==========line1======"
										+ line1);
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					} finally {
						try {
							is1.close();
							// 執行文件復制
							File f = new File(dpath + "\\" + file);
							f.mkdirs();// 創建目錄
							// 復制文件
							boolean b1 = copyFile(temppath + file
									+ "\\vtour\\tour.js", dpath + "\\" + file
									+ "\\tour.js");
							if (b1) {
								boolean b2 = copyFile(temppath + file
										+ "\\vtour\\tour.swf", dpath + "\\"
										+ file + "\\tour.swf");
								if (b2) {
									boolean b3 = copyFile(temppath
											+ file + "\\vtour\\tour.xml", dpath
											+ "\\" + file + "\\tour.xml");
									if (b3) {
										// 復制文件夾
										boolean b4 = copyFolder(
												temppath + file
														+ "\\vtour\\panos",
												dpath + "\\" + file + "\\panos");
										if (b4) {
											// 刪除臨時生成文件
											delFolder(temppath + file);
											// 修改krpano文件內容
											String xmlPath = dpath + "\\"
													+ file + "\\tour.xml";
											File xmlFile = new File(xmlPath);
											DocumentBuilderFactory dbFactory = DocumentBuilderFactory
													.newInstance();
											DocumentBuilder dBuilder;
											try {
												dBuilder = dbFactory
														.newDocumentBuilder();
												Document doc = dBuilder
														.parse(xmlFile);
												doc.getDocumentElement()
														.normalize();
												for (int i = 0; i < fn1.length; i++) {
													updateAttributeValue(doc,
															fn1[i], fn2[i]);
												}

												// update Element value
												updateElementValue(doc, title);

												// delete element
												deleteElement(doc);

												// add new element
												addElement(doc);

												updateAttributeColorValue(doc,
														"0x000000");
												addMusicElement(doc,music);
												// write the updated document to
												// file or console
												doc.getDocumentElement()
														.normalize();
												TransformerFactory transformerFactory = TransformerFactory
														.newInstance();
												Transformer transformer = transformerFactory
														.newTransformer();
												DOMSource source = new DOMSource(
														doc);
												StreamResult result = new StreamResult(
														new File(xmlPath));
												transformer.setOutputProperty(
														OutputKeys.INDENT,
														"yes");
												transformer.transform(source,
														result);
												//生成成功
												r.setMark("1");
											//	AdminService as = ContextUtil.getBean(AdminService.class, "adminService");
											//	as.updateRoom(r);
												/*System.out
														.println("XML file updated successfully");*/

											} catch (
													SAXException
													| ParserConfigurationException
													| IOException
													| TransformerException e1) {
												e1.printStackTrace();
												//生成失敗
												r.setMark("2");
											//	AdminService as = ContextUtil.getBean(AdminService.class, "adminService");
											//	as.updateRoom(r);
											}

										}
									}
								}
							}
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}.start();
			new Thread() {
				public void run() {
					BufferedReader br2 = new BufferedReader(
							new InputStreamReader(is2));
					try {
						String line2 = null;
						while ((line2 = br2.readLine()) != null) {
							if (line2 != null) {
								System.out.println("=AA==========line2======"
										+ line2);
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					} finally {
						try {
							is2.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}.start();
			p.waitFor();
			p.destroy();
		} else {
			System.out.println("上傳失敗");
		}

	}

	/**
	 * 復制單個文件
	 * 
	 * @param oldPath
	 *            String 原文件路徑 如:c:/fqf.txt
	 * @param newPath
	 *            String 復制后路徑 如:f:/fqf.txt
	 * @return boolean
	 */
	public static boolean copyFile(String oldPath, String newPath) {
		try {
			int bytesum = 0;
			int byteread = 0;
			File oldfile = new File(oldPath);
			if (oldfile.exists()) { // 文件存在時
				InputStream inStream = new FileInputStream(oldPath); // 讀入原文件
				FileOutputStream fs = new FileOutputStream(newPath);
				byte[] buffer = new byte[1444];
				int length;
				while ((byteread = inStream.read(buffer)) != -1) {
					bytesum += byteread; // 字節數 文件大小
					// System.out.println(bytesum);
					fs.write(buffer, 0, byteread);
				}
				inStream.close();
			}
		} catch (Exception e) {
			// System.out.println("復制單個文件操作出錯");
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 復制整個文件夾內容
	 * 
	 * @param oldPath
	 *            String 原文件路徑 如:c:/fqf
	 * @param newPath
	 *            String 復制后路徑 如:f:/fqf/ff
	 * @return boolean
	 */
	public static boolean copyFolder(String oldPath, String newPath) {
		try {
			(new File(newPath)).mkdirs(); // 如果文件夾不存在 則建立新文件夾
			File a = new File(oldPath);
			String[] file = a.list();
			File temp = null;
			for (int i = 0; i < file.length; i++) {
				if (oldPath.endsWith(File.separator)) {
					temp = new File(oldPath + file[i]);
				} else {
					temp = new File(oldPath + File.separator + file[i]);
				}

				if (temp.isFile()) {
					FileInputStream input = new FileInputStream(temp);
					FileOutputStream output = new FileOutputStream(newPath
							+ "/" + (temp.getName()).toString());
					byte[] b = new byte[1024 * 5];
					int len;
					while ((len = input.read(b)) != -1) {
						output.write(b, 0, len);
					}
					output.flush();
					output.close();
					input.close();
				}
				if (temp.isDirectory()) {// 如果是子文件夾
					copyFolder(oldPath + "/" + file[i], newPath + "/" + file[i]);
				}
			}
		} catch (Exception e) {
			// System.out.println("復制整個文件夾內容操作出錯");
			e.printStackTrace();
			return false;
		}
		return true;
	}

	// 刪除文件夾
	public static void delFolder(String folderPath) {
		try {
			delAllFile(folderPath); // 刪除完里面所有內容
			String filePath = folderPath;
			filePath = filePath.toString();
			File myFilePath = new File(filePath);
			myFilePath.delete(); // 刪除空文件夾
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static boolean delAllFile(String path) {
		boolean flag = false;
		File file = new File(path);
		if (!file.exists()) {
			return flag;
		}
		if (!file.isDirectory()) {
			return flag;
		}
		String[] tempList = file.list();
		File temp = null;
		for (int i = 0; i < tempList.length; i++) {
			if (path.endsWith(File.separator)) {
				temp = new File(path + tempList[i]);
			} else {
				temp = new File(path + File.separator + tempList[i]);
			}
			if (temp.isFile()) {
				temp.delete();
			}
			if (temp.isDirectory()) {
				delAllFile(path + "/" + tempList[i]);// 先刪除文件夾里面的文件
				delFolder(path + "/" + tempList[i]);// 再刪除空文件夾
				flag = true;
			}
		}
		return flag;
	}

	private static void addElement(Document doc) {
		NodeList employees = doc.getElementsByTagName("krpano");
		Element emp = null;

		// loop for each employee
		for (int i = 0; i < employees.getLength(); i++) {
			emp = (Element) employees.item(i);
			Element vtourskin = doc.createElement("include");
			vtourskin.setAttribute("url", "../skin/vtourskin.xml");
			emp.appendChild(vtourskin);
			Element skinselect = doc.createElement("include");
			skinselect.setAttribute("url", "../skinselect.xml");
			emp.appendChild(skinselect);
		}
	}
	private static void addMusicElement(Document doc,String music) {
		NodeList employees = doc.getElementsByTagName("krpano");
		Element emp = null;

		// loop for each employee
		for (int i = 0; i < employees.getLength(); i++) {
			emp = (Element) employees.item(i);
			Element musicEl = doc.createElement("action");
			musicEl.setAttribute("name", "bgsnd_action");
			musicEl.setAttribute("autorun", "onstart");
			musicEl.appendChild(doc.createTextNode("playsound(bgsnd, '"+music+"', 0);"));
			emp.appendChild(musicEl);
		}
	}
	private static void deleteElement(Document doc) {
		NodeList employees = doc.getElementsByTagName("krpano");
		Element emp = null;
		// loop for each employee
		for (int i = 0; i < employees.getLength(); i++) {
			emp = (Element) employees.item(i);
			Node genderNode = emp.getElementsByTagName("include").item(0);
			emp.removeChild(genderNode);
		}

	}

	private static void updateElementValue(Document doc, String title) {
		NodeList employees = doc.getElementsByTagName("krpano");
		Element emp = null;
		// loop for each employee
		for (int i = 0; i < employees.getLength(); i++) {
			emp = (Element) employees.item(i);
			emp.setAttribute("title", title);
		}
	}

	private static void updateAttributeValue(Document doc, String oldname,
			String newname) {
		NodeList employees = doc.getElementsByTagName("scene");
		Element emp = null;
		// loop for each employee
		for (int i = 0; i < employees.getLength(); i++) {
			emp = (Element) employees.item(i);
			if (emp.getAttribute("title").equals(oldname)) {
				emp.setAttribute("title", newname);
				break;
			}
		}
	}

	private static void updateAttributeColorValue(Document doc, String newname) {
		NodeList employees = doc.getElementsByTagName("skin_settings");
		Element emp = null;
		// loop for each employee
		for (int i = 0; i < employees.getLength(); i++) {
			emp = (Element) employees.item(i);
			emp.setAttribute("design_bgcolor", newname);
			emp.setAttribute("design_bgalpha", "0.8");
		}
	}
}  

去掉mian函數的注釋,啟動一下,看看執行結果,這段代碼相當於手動拖動圖片到.bat上,其中可以修改krpano下的templates下的vtour-multires.config文件

# basic settings
include basicsettings.config
panotype=sphere
# panotype=autodetect
hfov=360
makescenes=true

  自動生成一種固定的全景圖(柱型,球型...)

Ok,看一下生成文件中哪些是固定的公用的

@Controller
public class FileLibraryController {
         /**
             *    @Description:  從文件中獲取全景圖
             *    @Date:  17:13  2018/7/5
             *    @Params:   * @param null
             */

    @RequestMapping("/all")
    public ModelAndView list(Model model){
       // List<File> wjList = new ArrayList<File>();//新建一個文件集合
        List<PanoramaDO> list=new ArrayList<>();
        File file=new File(ConstantBank.PANORAMA_BANK_URL);
        ModelAndView mv=new ModelAndView("list.html");
        File[] fileList = file.listFiles();//將該目錄下的所有文件放置在一個File類型的數組中
        for (int i = 0; i < fileList.length; i++) {
            if (fileList[i].isDirectory()) {//判斷是否為文件
              //  wjList.add(fileList[i]);
                String directoryName=fileList[i].getName();

                if (!directoryName.equals(ConstantBank.EXCLUSIVE_SECOND_DIR_NAEM)&&!directoryName.equals(ConstantBank.EXCLUSIVE_FIRST_DIR_NAME)){
                    //分割文件名
                    String title =directoryName.substring(directoryName.indexOf(",")+1);
                    PanoramaDO panoramaDO=new PanoramaDO();
                    panoramaDO.setId(directoryName);
                    if (title==null||title.equals("")){
                        panoramaDO.setTitle("未命名");
                    }else {
                        panoramaDO.setTitle(title);
                    }
                    list.add(panoramaDO);
                    //System.out.println(directoryName);
                    //System.out.println(title);
                }


            }
        }
        System.out.println(list);
        mv.addObject("list",list);
        return mv;

    }

    @RequestMapping("/{id}/temp")
    public ModelAndView jump(@PathVariable("id") String vid){

        ModelAndView mv =new ModelAndView("vr.html");
        //mv.getView().
        mv.addObject("vid",vid);
        String title =vid.substring(vid.indexOf(",")+1);
        if (title==null||title.equals("")){
            mv.addObject("title","未命名");
        }else {
            mv.addObject("title",title);
        }
        System.out.println("===>"+vid);
       return mv;
    }

    @RequestMapping("/compound/{fileName}/{title}")
    public ModelAndView compound(@PathVariable("fileName") String fileName,
                            @PathVariable("title") String title){

        ModelAndView mv =new ModelAndView("redirect:/all");
        //mv.getView().
        Room r = new Room();
        //項目的位置
        String dpath = "D:\\tupian\\vshow";
        //全景圖的文件名
        String file = fileName;
        String[] fn1 = { "2",
                "3" };
        String[] fn2 = { "客廳", "卧室","大客廳" };
        //String title = "哈哈哈哈哈哈哈哈";
        String music = "vshow/backgroundmusic/default.mp3";
        try {
            CmdBat.setKrpano(r,dpath, file, fn1, fn2, title,music);
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("上傳失敗");
        }



        return mv;
    }


}

 vr.html  公用的vr.html

<!--<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!DOCTYPE html>
<html>
<head>
<base href="/">
	<title th:text="${title}"> </title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" />
	<meta name="apple-mobile-web-app-capable" content="yes" />
	<meta name="apple-mobile-web-app-status-bar-style" content="black" />
	<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
	<meta http-equiv="x-ua-compatible" content="IE=edge" />
	<link rel="shortcut icon" href="images/favicon.png">
	<style>
		@-ms-viewport { width:device-width; }
		@media only screen and (min-device-width:800px) { html { overflow:hidden; } }
		html { height:100%; }
		body { height:100%; overflow:hidden; margin:0; padding:0; font-family:Arial, Helvetica, sans-serif; font-size:16px; color:#FFFFFF; background-color:#000000; }
	</style>
</head>
<body>
<div style="position: absolute;z-index: 1;margin-top: 10px;margin-left: 10px">
<a href="/all"><img src="img/logo.jpg" style="height: 50px"></a>
</div>

<script th:src="'/vshow/'+${vid }+'/tour.js'"></script>
<div id="pano" style="width:100%;height:100%;">
	<noscript><table style="width:100%;height:100%;"><tr style="vertical-align:middle;"><td><div style="text-align:center;">ERROR:<br/><br/>Javascript not activated<br/><br/></div></td></tr></table></noscript>
	<script>
        /*<![CDATA[*/

        var vid = "[[${vid}]]";
        embedpano({swf:"/vshow/"+vid+"/tour.swf", xml:"/vshow/"+vid+"/tour.xml", target:"pano", html5:"prefer", mobilescale:1.0, passQueryParameters:true});

     /*   ]]>*/

	</script>
</div>

</body>
</html>

  upload.html  這是文件上傳以及回顯,下面的js等待加載過度

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
    <link rel="stylesheet" type="text/css" href="css/normalize.css" />
    <link rel="stylesheet" type="text/css" href="css/default.css">
    <!--<link rel="stylesheet" href="css/bootstrap.min.css">-->
    <link rel="stylesheet" href="css/demo.css">
    <link rel="stylesheet" href="css/fakeLoader.css">

    <style>
        .uploadImgBtn {

            width: 100px;
            height: 100px;
            cursor: pointer;
            position: relative;
            background: url("img/plus.png") no-repeat;
            -webkit-background-size: cover;
            background-size: cover;
        }

        .uploadImgBtn .uploadImg {
            position: absolute;
            right: 0;
            top:0;
            width: 100%;
            height: 100%;
            opacity: 0;
            cursor: pointer;
        }
        //這是一個用做回顯的盒子的樣式
          .pic{
              width: 100px;
              height: 100px;
          }
        .pic img {
            width: 200px;
            height: 100px;
        }
    </style>


</head>

<body>


    <div class="fakeloader"></div>



<form action="/upload" method="post" enctype="multipart/form-data">
    <div class="uploadImgBtn" id="uploadImgBtn">
        <input class="uploadImg" type="file" name="file" multiple id="file">
    </div>
    <div>合成全景圖片的名字<input type="text" name="title" id="title"></div>
    <input type="submit" onclick="haha()" value="上傳">
</form>

    <script src="js/jquery-1.11.0.min.js"></script>
    <script src="js/fakeLoader.min.js"></script>


<script>

    function haha() {
        var html01='<h4>全景圖正在合成請稍等...</h4>';
        $(".fakeloader").append($(html01));
        $(".fakeloader").fakeLoader({
            timeToHide:1200000,
            bgColor:"#d9d2e9",
            spinner:"spinner2"
        });
    }
    $(document).ready(function(){





        //為外面的盒子綁定一個點擊事件
        $("#uploadImgBtn").click(function(){
            /*
            1、先獲取input標簽
            2、給input標簽綁定change事件
            3、把圖片回顯
             */
//            1、先回去input標簽
            var $input = $("#file");
            console.log($input)
//            2、給input標簽綁定change事件
            $input.on("change" , function(){
                console.log(this)
                //補充說明:因為我們給input標簽設置multiple屬性,因此一次可以上傳多個文件
                //獲取選擇圖片的個數
                var files = this.files;
                var length = files.length;
                console.log("選擇了"+length+"張圖片");
                //3、回顯
                $.each(files,function(key,value){
                    //每次都只會遍歷一個圖片數據
                    var div = document.createElement("div"),
                        img = document.createElement("img");
                    div.className = "pic";

                    var fr = new FileReader();
                    fr.onload = function(){
                        img.src=this.result;
                        div.appendChild(img);
                        document.body.appendChild(div);
                    }
                    fr.readAsDataURL(value);
                })

            })

            //4、我們把當前input標簽的id屬性remove
            $input.removeAttr("id");
            //我們做個標記,再class中再添加一個類名就叫test
            var newInput = '<input class="uploadImg test" type="file" name="file" multiple id="file">';
            $(this).append($(newInput));

        })

    })

</script>
</body>
</html>

  這寫到這把,后續會弄全景漫游....這些都要整合項目里面

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM