def get_offset_and_crop_size(
    width: int,
    height: int,
    h_mat: np.ndarray,
    crop_percent: float,
    crop_off_x: float,
    crop_off_y: float,
    ratio: float,
    pts: np.ndarray | None = None,
) -> tuple[float, float, float]:
    """
    Compute offsets and crop size for perspective transform.

    :param w: Width of image.
    :param h: Height of image.
    :param H: Homography matrix.
    :param crop_percent: Percentage for cropping.
    :param crop_off_x: Cropping x offset.
    :param crop_off_y: Cropping y offset.
    :param ratio: Focal length to distance ratio for scaling.
    :param pts: pts to include in the crop.
    :return: x offset, y offset, crop size.
    """
    pts_flag = pts is not None
    if pts is not None:
        pts_copy = [point.copy() for point in pts]
        for i in range(len(pts)):
            pts_copy[i][0, 0] = pts_copy[i][0, 0] * width / pts_copy[i][2, 0]
            pts_copy[i][1, 0] = pts_copy[i][1, 0] * height / pts_copy[i][2, 0]
            pts_copy[i][2, 0] = pts_copy[i][2, 0] * 1.0 / pts_copy[i][2, 0]

    else:
        pts_copy = [
            np.array([[0], [0], [1.0]]),
            np.array([[0], [height], [1.0]]),
            np.array([[width], [0], [1.0]]),
            np.array([[width], [height], [1.0]]),
        ]

    min_x = width
    min_y = height
    max_x = 0
    max_y = 0

    for point in pts_copy:
        new_pt = np.matmul(h_mat, point)
        new_pt /= new_pt[2, 0]

        if new_pt[0, 0] < min_x:
            min_x = new_pt[0, 0]
        if new_pt[0, 0] > max_x:
            max_x = new_pt[0, 0]
        if new_pt[1, 0] < min_y:
            min_y = new_pt[1, 0]
        if new_pt[1, 0] > max_y:
            max_y = new_pt[1, 0]

    if pts_flag:
        if (max_x - min_x) / (max_y - min_y) < width / height:  # result is tall and narrow
            diff_in_size = (max_y - min_y) / height * width - (max_x - min_x)
            orig_size = max_y - min_y if width > height else (max_y - min_y) / height * width
            crop_size = int(round(orig_size * (1.0 - crop_percent)))
            y_off = -min_y - int(round(crop_percent / 2 * orig_size))
            x_off = -min_x + int(round(diff_in_size / 2 - crop_percent / 2 * orig_size))

        else:  # result is wide and short
            diff_in_size = (max_x - min_x) / width * height - (max_y - min_y)
            orig_size = max_x - min_x if height > width else (max_x - min_x) / width * height
            crop_size = int(round(orig_size * (1.0 - crop_percent)))
            x_off = -min_x - int(round(crop_percent / 2 * orig_size))
            y_off = -min_y + int(round(diff_in_size / 2 - crop_percent / 2 * orig_size))

        return x_off + crop_off_x * crop_size, y_off + crop_off_y * crop_size, crop_size

    min_x -= int((width * ratio - (max_x - min_x)) // 2)
    min_y -= int((height * ratio - (max_y - min_y)) // 2)

    crop_size = int(round((1.0 - crop_percent) * min(width, height) * ratio))

    return (
        -min_x - int(round(crop_percent / 2 * width * ratio)),
        -min_y - int(round(crop_percent / 2 * height * ratio)),
        crop_size,
    )

    Compute offsets and crop size for perspective transform.

    :param w: Width of image.
    :param h: Height of image.
    :param H: Homography matrix.
    :param crop_percent: Percentage for cropping.
    :param crop_off_x: Cropping x offset.
    :param crop_off_y: Cropping y offset.
    :param ratio: Focal length to distance ratio for scaling.
    :param pts: pts to include in the crop.
    :return: x offset, y offset, crop size.
    
art/attacks/evasion/graphite/utils.py