作者: zzh

  • 我评《肖申克的救赎》

    R-C.jpeg

    简评:掐头去尾,才可称影史第一

    我为何这样评价?

    1 开头部分如果想简单地带过,可以用倒叙手法,直接在监狱开场?电影里的内容过于拖沓,试图让观众代入被冤枉入狱难以复盘的绝望心情。但是长短节奏和输出内容不符合。

    2 结尾很尴尬,甚至有点像我拍的。试图讲述主角的结局,也要圆满之前埋下的伏笔。些许牵强,不过聊胜于无吧。

    这两段我感觉内容节奏没有把握好。毕竟是双料榜单第一名,我属于是强行鸡蛋里挑骨头。不过好处就是降低了理解门槛?让人理解的艺术才是艺术,毕竟是商业电影不是艺术片(试图往回圆。

    整体连贯得当,节奏紧凑分明。故事中间甚至可以用明朗的镜头带来一些惊悚的压抑感。没有过多写安迪挖掘隧道的内容,用变白的头发和周遭的变化来描述事情的发生,巧妙。最后拿出带小锤凹槽的圣经,一切恍然大悟,深深地雨夜,却仿佛一片光明。好故事,好导演。

    结尾烂梗:主角不叫肖申克

  • 用PHP做一个自己的访问统计

    原因

    为什么自己写

    因为在自己域下,可控&不会被常见的去广告插件屏蔽

    为什么选择PHP

    我曾经试图用CSS方法实现,奈何技术有限,且我不是专业的前端和后端。不过如果有兴趣的朋友可以看看这个文章 https://herman.bearblog.dev/how-bear-does-analytics-with-css/ 。这个作者简单介绍了自己如何使用CSS方法进行统计并计算PV之类的数据。

    且我希望是放在主题文件下可以直接运行和使用,没有想在服务端运行docker或者其他程序

    它有什么功能

    机制简单的统计,访问+1,刷新+1,如此而已

    开始

    创建数据库

    首先你需要创建数据库,如果你有数据库的前提下,请使用下面的语句新建一张这样的表:

    CREATE TABLE statistics (
        id INT AUTO_INCREMENT PRIMARY KEY,
        site_name VARCHAR(255) NOT NULL,
        visit_count BIGINT NOT NULL DEFAULT 0
    );

    这个表里只包含了一个网站地址和这个网站相对应的访问数量。它的结构大概是:

    sitename visit_count
    banzhuanriji.com 123

    然后我们初始化一下:

    INSERT INTO statistics (site_name, visit_count) VALUES ('banzhuanriji.com', 0);

    新建一行banzhuanriji.com的数据,且把默认数据(访问量)设置为0

    实现代码

    首先在你的主题目录下创建一个counter.php的文件,文件内容如下

    <?php
    $servername = "localhost";
    $username = "statistics";
    $password = "password";
    $dbname = "statistics";
    
    // 创建连接
    $conn = new mysqli($servername, $username, $password, $dbname, 3306);
    
    // 检查连接
    if ($conn->connect_error) {
        die("连接失败,请稍后再试。");
    }
    
    // 指定网站名称
    $site_name = 'banzhuanriji.com'; // 这里修改为需要统计的网站名称
    
    // 更新浏览次数
    $sql = "UPDATE statistics SET visit_count = visit_count + 1 WHERE site_name = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("s", $site_name); // 绑定网站名称
    $stmt->execute();
    
    // 获取当前浏览次数
    $sql = "SELECT visit_count FROM statistics WHERE site_name = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("s", $site_name); // 绑定网站名称
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result->fetch_assoc();
    $current_views = $row['visit_count'] ?? 0; // 如果没有找到,默认值为 0
    
    // 关闭连接
    $stmt->close();
    $conn->close();
    ?>
    
    <br>当前浏览次数: <?php echo htmlspecialchars($current_views); ?>

    然后你只需要在需要的地方引用counter.php即可。下面依然以final主题为示例:

    <?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
    
    <footer style="margin:50px 0px">
    
    <span id="footer-directive">
    
    <?php $this->options->addfoot() ?>
    
    </span>
    
    <span>
    
    &copy; 2024 <a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title(); ?></a>
    
    </span>
    <?php include 'counter.php'; ?>
    </footer>
    
    </body>
    
    </html>

    然后就可以实现类似卜蒜子一样的统计效果了。而且没有使用到JS,不会被去广告插件拦截。

    其它

    如果你有兴趣可以自己动手实现一下。你也可以把数据库信息替换成typecho的数据库信息,这样就避免了新操作一个数据库的逻辑,并且可以实现使用typecho的Db.php文件操作数据库。下面给一个简单的示例,具体的实现逻辑和读写权限请自行斟酌完成。

    <?php
    // 引入 Typecho 的数据库类
    require_once '/www/wwwroot/banzhuanriji.com/usr/Typecho/Db.php'; // 确保路径正确
    
    // 获取数据库连接
    $db = Typecho_Db::get();
    
    // 指定网站名称
    $site_name = 'example.com'; // 这里修改为需要统计的网站名称
    
    try {
        // 更新特定网站的浏览次数
        $db->query("UPDATE `statistics` SET `visit_count` = `visit_count` + 1 WHERE `site_name` = ?", $site_name);
    
        // 获取当前浏览次数
        $result = $db->fetchRow($db->select('visit_count')->from('statistics')->where('site_name = ?', $site_name));
        $current_views = $result['visit_count'];
    } catch (Exception $e) {
        // 处理异常
        error_log($e->getMessage());
        $current_views = 0; // 如果出错,设置为 0
    }
    ?>
    
    <div class="counter">
        <h1><?php echo htmlspecialchars($site_name); ?> 当前浏览次数: <?php echo htmlspecialchars($current_views); ?></h1>
    </div>
  • Android第三方APP拍照开发指南

    现状分析

    在 Android 第三方 APP 拍照功能的实现现状中,我们不难发现存在不少问题,其中图像不清晰是较为突出的一个。经过深入研究发现,这主要归因于大部分 APP 采用 preview(预览)截图的方式来充当拍照结果。这种做法存在很大的局限性,preview 截图只是对相机预览画面的简单截取,并没有经过相机真正拍照时完整且精细的图像生成和处理流程。相机拍照时,会利用复杂的光学和电子元件精确地捕捉光线信息,然后经过一系列诸如自动对焦、自动曝光、色彩校正、降噪等处理环节,而 preview 截图往往缺失这些关键步骤,所以导致拍摄出的图像质量远低于预期。

    所以,这种方法不仅导致图片质量差,而且用户体验也不佳。由于没有充分利用Android系统的相机功能,很多开发者在实现拍照时面临诸多困难。大概归为下面几个比较突出的问题:

    • 图片质量低:预览截图无法获取高质量的照片。
    • 用户体验差:拍照过程不够流畅,用户可能需要多次尝试才能获得满意的照片。
    • 功能限制:无法使用相机的高级功能,如闪光灯、焦距调整等。

    正确的拍照方式

    一、调用系统相机拍照

    当第三方 APP 在 Android 系统中调用相机拍照时,本质上是通过与系统相机服务交互来实现的。应用会发送一个特定的 Intent 来唤起系统相机应用。这个 Intent 可以携带一些参数,比如指定输出的图像格式、存储位置等。系统相机应用启动后,它会初始化相机硬件,包括启动相机传感器、配置镜头参数等。当用户触发拍照操作时,相机传感器开始工作,光线通过镜头在传感器上成像。传感器将光信号转换为电信号,再经过模数转换为数字信号。这些数字信号随后在相机内部的图像处理器中经过一系列复杂算法的处理,如根据光线条件调整曝光值、通过自动对焦算法使图像清晰对焦、对色彩进行平衡处理以保证色彩的准确性。处理完成后,图像数据会根据设定的格式(如 JPEG)存储在指定位置,并通过 Intent 将拍摄结果回传给第三方应用,这个过程需要确保应用有相应的存储权限和读取权限。

    那么,在Android中,调用系统相机拍照的逻辑相对简单,主要通过Intent来实现。以下是基本的实现步骤:

    1. 创建Intent:使用MediaStore.ACTION_IMAGE_CAPTURE创建一个拍照的Intent。
    2. 传递文件URI:指定照片存储的位置,确保拍摄的照片能正确保存。
    3. 启动Activity:通过startActivityForResult启动相机界面。
    4. 处理结果:在onActivityResult中处理拍照后的结果。

    简单的实现步骤如下:

    private static final int REQUEST_IMAGE_CAPTURE = 1;
    private Uri photoURI;
    
    private void dispatchTakePictureIntent() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            // 创建一个文件来存储照片
            File photoFile = createImageFile();
            if (photoFile != null) {
                photoURI = FileProvider.getUriForFile(this,
                        "com.example.android.fileprovider",
                        photoFile);
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
            }
        }
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            // 处理拍照结果
            // photoURI指向的文件就是拍摄的照片
        }
    }

    二、通过Camera2软件包进行拍照

    android.hardware.camera2

    首先一张图来说明一下构建相机APP的正确逻辑:

    2018 I/O大会

    Camera2 API提供了对相机硬件的深入控制,支持更复杂的功能,如手动对焦、曝光控制等。使用Camera2 API的基本步骤如下:

    1. 获取CameraManager:使用CameraManager获取相机服务。
    2. 打开相机:通过openCamera方法打开相机。
    3. 创建CaptureSession:配置并创建用于捕获图像的会话。
    4. 拍照:使用CaptureRequest配置拍照参数并执行。

    具体实现代码(上述步骤为简要步骤,具体请参考代码):

    申请权限

    <uses-permission android:name="android.permission.CAMERA" />

    获取可用相机设备列表

    CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        String[] cameraIds = manager.getCameraIdList();
        for (String cameraId : cameraIds) {
            CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
            // 这里可以进一步检查相机特性,如是否支持特定功能等
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }

    配置相机参数

    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            // 相机打开成功,可以进行参数配置和开始预览、拍照等操作
            try {
                CameraCaptureSession.StateCallback sessionCallback = new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(CameraCaptureSession session) {
                        // 会话配置成功,可以设置拍照请求等
                        try {
                            CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                            // 设置图像格式、自动对焦模式等参数
                            builder.addTarget(imageReader.getSurface());
                            CaptureRequest request = builder.build();
                            session.capture(request, null, null);
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                    }
    
                    @Override
                    public void onConfigureFailed(CameraCaptureSession session) {
                        // 会话配置失败处理
                    }
                };
                camera.createCaptureSession(Arrays.asList(imageReader.getSurface()), sessionCallback, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void onDisconnected(CameraDevice camera) {
            // 相机断开连接处理
            camera.close();
        }
    
        @Override
        public void onError(CameraDevice camera, int error) {
            // 相机出现错误处理
            camera.close();
        }
    };

    进行拍照

    CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
    builder.addTarget(imageReader.getSurface());
    // 设置自动对焦模式为自动
    builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
    // 设置曝光模式为自动
    builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
    CaptureRequest request = builder.build();
    session.capture(request, null, null);

    总结简单的实现方法

    private void openCamera() {
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            String cameraId = manager.getCameraIdList()[0]; // 获取后置相机ID
            manager.openCamera(cameraId, stateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    
    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            // 相机打开成功,开始拍照
            // 此处可以配置CaptureSession
        }
    
        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            camera.close();
        }
    
        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            camera.close();
        }
    };

    通过CameraX软件包进行拍照

    CameraX 是一个 Jetpack 库。同样更方便地提供了包括但不限于相机的预览,分析,视频和图片的拍摄的api。下面是使用代码:

    添加依赖

    def camerax_version = "1.2.0"
    implementation "androidx.camera:camera-core:$camerax_version"
    implementation "androidx.camera:camera-camera2:$camerax_version"
    def camerax_version = "1.1.0-alpha07"
    implementation "androidx.camera:camera-core:$camerax_version"
    implementation "androidx.camera:camera-camera2:$camerax_version"
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    implementation "androidx.camera:camera-view:$camerax_version"
    

    初始化CameraX

    ProcessCameraProvider.getInstance(this).addListener(() -> {
        try {
            cameraProvider = ProcessCameraProvider.getInstance(context).get();
            bindCameraUseCases();
        } catch (ExecutionException | InterruptedException e) {
            // 异常处理
        }
    }, ContextCompat.getMainExecutor(this));

    配置和使用相机示例

    // 创建 ImageCapture 和 Preview 等相机用例,并将它们绑定到相机设备上
    private void bindCameraUseCases() {
        Preview preview = new Preview.Builder()
              .build();
        preview.setSurfaceProvider(viewFinder.getSurfaceProvider());
    
        ImageCapture imageCapture = new ImageCapture.Builder()
              .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
              .build();
    
        CameraSelector cameraSelector = new CameraSelector.Builder()
              .requireLensFacing(CameraSelector.LENS_FACING_BACK)
              .build();
    
        try {
            cameraProvider.unbindAll();
            camera = cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, preview, imageCapture);
        } catch (Exception e) {
            // 绑定失败处理
        }
    }

    拍照操作

    imageCapture.takePicture(ContextCompat.getMainExecutor(this), new ImageCapture.OnImageCapturedCallback() {
        @Override
        public void onCaptureSuccess(@NonNull ImageProxy image) {
            // 拍照成功处理,这里可以获取图像数据并进一步处理
            super.onCaptureSuccess(image);
            image.close();
        }
    
        @Override
        public void onError(@NonNull ImageCaptureException exception) {
            // 拍照错误处理
            super.onError(exception);
        }
    });

    依然,提供一个简单的示例

    private void startCamera() {
        CameraX.bindToLifecycle(this, preview, imageCapture);
    }
    
    // 配置预览
    Preview preview = new Preview.Builder().build();
    
    // 配置拍照
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    小结

    在Android应用开发中,实现高质量的拍照功能至关重要。通过合理选择相机API(如Camera2和CameraX),开发者可以提供更高质量的照片和更流畅的用户体验。还有很多内容本文没有提到,例如:HDR,多相机(Multi-Camera)调用,屏幕闪烁设置,视频流(相机帧)的优化,CameraX的Extensions API 。

    目前来看,android的相机和iOS一对比,真是一坨越堆越高的大屎山!

  • 深入理解 Git 中的 cherry-pick 操作

    在使用 Git 进行版本控制的过程中,我们常常会遇到需要从一个分支中选取特定的提交并应用到另一个分支的情况,这时候,cherry-pick 操作就派上用场了。

    (更多…)

  • 内网穿透工具横向对比

    在网络环境中,内网穿透工具为我们提供了便捷的方式来访问内部网络资源。本文将对 cpolar、花生壳、SAKURA FRP、NATAPP、飞鸽、网云穿、闪库、内网云、快解析、nat123 和 i996 等工具进行横向对比,分析它们的优缺点。

    (更多…)

  • 对跖点

    其实很久之前也会有一闪而过的想法,地球另一端的人不知道是谁,他的生活是怎么样的。昨天在网上看视频看到了这个概念。只需要简单地计算就可以知道地球上离你最远地点在哪里。

    (更多…)

  • 世界,您好!

    欢迎使用 WordPress。这是您的第一篇文章。编辑或删除它,然后开始写作吧!

  • 小米 Redmi note 14 pro + 的糟糕体验

    发布会第二天线下购买两台,现在遇到的问题

    赠送的五大保障超过一周可退货时间依然未到账

    2024 年 9 月 27 日激活,首发赠送的‘五大保障’未到账,客服称需要我联系小米之家。联系小米之家,小米之家称都有,但是查询不到(别人的同款手机可以查询到)。

    相册必现的恶性 bug ,成本极高的反馈流程和小米超级分散的售后部门

    带水印拍照后,相册打开超动态显示,你会发现小米的 HDR 就是在图片上加了个 灰色滤镜 。现在的问题是,滤镜的位置错了,导致我看照片,照片会显示一道白边。(也就是蒙版应该居上,而不是居下,因为下面有一截水印,具体效果请看链接附图)

    在线客服反馈,无果。后被多次打电话询问具体状况。我在心平气和地沟通完所有复现步骤,并告知所有系统&软件版本信息之后,客服告知无法反馈到技术部门,因为他们不是一个部门。说我需要到小米社区/服务与反馈 APP 进行反馈。笑。

    于是在小米社区 app 进行反馈。被告知需要‘在服务与反馈 App 中勾选上传问题日志提交下反馈’。但是 我的‘服务与反馈’app 打开闪退 ……

    具体效果请看(三张图拼接,分别为 0.6X ,1X ,2.5X ):

    目前可以忍受的问题

    卡。各种卡。刚买前几天发热厉害(在戴了官方赠送的硅胶壳的前提下),我猜测小米知道这个问题,所以选择在手机大批量发布之前,天气降温之后发布。

    我关闭了所有 APP 的自启动权限,全部设置为禁止后台运行,目前发现:米家,夸克浏览器,酷安,剪映依然可以后台自启运行(之前的 MIUI14 貌似没有这个问题)。或许是被其他 APP 唤醒,但我不知道是哪个 APP ,因为小米 HyperOS 貌似删除了禁止唤醒其他 APP 或服务的开关,目前只剩下很鸡肋的链式唤醒(用户主动跳转)开关。解决方案:我给这几个软件卸载了。

    按下拍照键之后疯狂卡顿。如果是用 2.5x 镜头拍照貌似卡的更厉害。就是卡到取景框不动的那种卡。

    成像效果不尽人意。光线稍暗,宣传的光影猎人 800 ,实际超级大涂抹带你梦回 2014 。

    绝非黑稿,本人亲身经历。